Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

NHibernate vs. NEO: Face to Face. Part I

0.00/5 (No votes)
20 Jun 2005 1  
The first article in the Nhibernate vs. NEO series. Introduction to NEO O/R mapping tool.

Introduction

There are many open source O/R mapping frameworks that have been appearing on .NET platform during the last year. Most of them are ugly and only a few of them are ready for real-world development. Among the open source solutions, NEO and NHibernate are at the top in the O/R mapping world. That is why we will compare them in this series of articles, face to face.

In this article we will introduce NEO Framework. I have very good experience with NEO, since we use it in TargetProcess project - ASP.NET based agile project management system. The project is quite mature and this is a real experience from a real production project.

Short Intro

NEO

NEO is a framework originally created for .NET. It is quite simple and quite featured to solve almost all the usual O/R mapping problems. Please visit the site: codehaus.

NHibernate

NHibernate is a .NET port of a very popular Java based Hibernate framework. There is a very clear trend these years to port all good tools from Java world to .NET. Hibernate is widely used and has been evolved into one of the most powerful O/R mapping solutions. Please visit: NHibernate.

NEO Framework

Usage of almost any O/R mapping tool is quite uniform with some slight deviations. These are the six steps to start using NEO:

  1. Create domain model (UML diagram, text description in your mind).
  2. Create domain descriptor in XML format for NEO.
  3. Generate C# classes and SQL queries for domain model.
  4. Include C# classes into project.
  5. Create the database and execute SQL queries.
  6. Use NEO Framework to handle persistence objects.

Creating mappings

Any O/R mapping tool works with domain model. Some of them are closer to database while others are closer to domain model. NEO is on the middle ground. You may create a database and generate domain model descriptor file or you may create domain model descriptor and then generate the database. I prefer the second way.

Let's take a very simple domain mode. This is a kind of very basic bug tracking system. You may add a project, add bugs, assign them to users and add comments to the bugs. Here is the class diagram:

Now we should translate this fancy UML picture to NEO language - model descriptor in XML format. We are starting with project class mapping. As you know, each class in the domain model has a table in a database (we'll not consider inheritance for now). So, all the objects of Project class will be stored in a table projects.

<?xml version="1.0" encoding="ISO-8859-1" standalone="no" ?>
<!DOCTYPE database SYSTEM "Resources/norque.dtd">
<database name="tp" package="Neo.Web.Model" 
   defaultIdMethod="native" defaultJavaNamingMethod="underscore">
<table name="projects" javaName="Project">
. . .
</table>
</database>

Well, maybe you are slightly confused with the javaName property used in C# based framework. But a rational description exists as you may have guessed. The fact is that NEO uses DTD of Torque O/R mapping tool that was written in Java and for Java.

We have mapped the class and now we should do the same for the class properties. Our Project class has four properties:

  • Name,
  • Description,
  • StartDate,
  • ProjectId

In the database, ProjectId will be an auto incremented Primary Key column. Mapping for all the other properties is very simple.

<table name="projects" javaName="Project">
    <column name="project_id" primaryKey="true" 
        autoIncrement="true" required="true" type="INTEGER" />
    <column name="name" required="true" 
               type="VARCHAR" size="150" />        
    <column name="description" required="false" 
                        type="VARCHAR" size="600" />
    <column name="start_date" required="true" 
                                     type="DATE" />
</table>

Let's skip mapping of User class since it is almost the same and look right into the Bug class. According to the Domain model, each project has a collection of Bugs. This is a one-to-many relation and in a relational database realm, represented by a simple foreign key.

<table name="bugs" javaName="Bug">
    <column name="bug_id" primaryKey="true" autoIncrement="true" 
                                 required="true" type="INTEGER" />
    <column name="name" required="true" type="VARCHAR" size="250" />
    <column name="description" required="false" 
                        type="VARCHAR" size="2500" />
    <column name="effort" required="false" type="DECIMAL" />
    <column name="status" required="true" type="INTEGER" />
    <column name="project_id" required="true" type="INTEGER" />
    <foreign-key foreignTable="projects" name="Project" 
                                       onDelete="cascade">
        <reference local="project_id" foreign="project_id" />
    </foreign-key>
</table>

All the Bug objects are stored in bugs table, bug_id is the primary key and project_id is the foreign key in projects table.

<foreign-key foreignTable="projects" name="Project" onDelete="cascade">

Name attribute sets the name of relation. In this case each Bug object will have a reference to Project object and in the code you will be able to write something like this:

String projectName = bug.Project.Name;
<reference local="project_id" foreign="project_id" />

Reference sets columns for foreign key. In this case project_id column should exist in bugs table and in projects table.

If we want to have collection of bugs in a Project object, we should add information about bugs into the Project class definition:

<table name="projects" javaName="Project">
    . . .
 <iforeign-key foreignTable="bugs" name="Bugs" onDelete="cascade">
        <ireference local="project_id" foreign="project_id" />
    </iforeign-key>
</table>
public ICollection GetBugs()
{
    return new BugList(this.Bugs);
}

Finally, the full mapping will look like this:

<?xml version="1.0" encoding="ISO-8859-1" standalone="no" ?>
<!DOCTYPE database SYSTEM "Resources/norque.dtd">
<database name="tp" package="Neo.Web.Model" defaultIdMethod="native" 
                                defaultJavaNamingMethod="underscore">
    
    <table name="projects" javaName="Project">
        <column name="project_id" primaryKey="true" autoIncrement="true" 
                                        required="true" type="INTEGER" />
        <column name="name" required="true" type="VARCHAR" size="150" />
        <column name="description" required="false" type="VARCHAR" 
                                                         size="600" />
        <column name="start_date" required="true" type="DATE" />
        <iforeign-key foreignTable="bugs" name="Bugs" onDelete="cascade">
            <ireference local="project_id" foreign="project_id" />
        </iforeign-key>
    </table>
    
    <table name="users" javaName="User">
        <column name="user_id" primaryKey="true" autoIncrement="true" 
                                     required="true" type="INTEGER" />
        <column name="name" required="true" type="VARCHAR" size="150" />
        <column name="login" required="true" type="VARCHAR" size="50" />
        <column name="password" required="true" type="VARCHAR" 
                                                       size="50" />
    </table>
    
    <table name="bugs" javaName="Bug">
        <column name="bug_id" primaryKey="true" autoIncrement="true" 
                                      required="true" type="INTEGER" />
        <column name="name" required="true" type="VARCHAR" size="250" />
        <column name="description" required="false" type="VARCHAR" 
                                                        size="2500" />
        <column name="effort" required="false" type="DECIMAL" />
        <column name="status" required="true" type="INTEGER" />
        <column name="project_id" required="true" type="INTEGER" />
        <column name="user_id" required="true" type="INTEGER" />
        <foreign-key foreignTable="projects" name="Project" 
                                             onDelete="cascade">
            <reference local="project_id" foreign="project_id" />
        </foreign-key>
        <foreign-key foreignTable="users" name="Owner">
            <reference local="user_id" foreign="user_id" />
        </foreign-key>
        <iforeign-key foreignTable="comments" name="Comments" 
                                                onDelete="cascade">
            <ireference local="bug_id" foreign="bug_id" />
        </iforeign-key>
    </table>
    
    <table name="comments" javaName="Comment">
        <column name="comment_id" primaryKey="true" autoIncrement="true" 
                                       required="true" type="INTEGER" />
        <column name="text" required="false" type="VARCHAR" size="1000" />
        <column name="date" required="true" type="DATE" />
        <column name="user_id" required="true" type="INTEGER" />
        <column name="bug_id" required="true" type="INTEGER" />
        <foreign-key foreignTable="users" name="User">
            <reference local="user_id" foreign="user_id" />
        </foreign-key>
        <foreign-key foreignTable="bugs" name="Bug" onDelete="cascade">
            <reference local="bug_id" foreign="bug_id" />
        </foreign-key>
    </table>
</database>

Generating model classes

Now we have all the four classes mapped and ready to generate SQL and �# code for them. NEO has a code generation tool neo.exe that can be found in the [neo root folder]\src\Tools\CmdLineTool folder. It is convenient to create BAT file with all the required commands.

neo.exe -r Resources -f -sql true -sqldrop true -o Sql model.xml

neo.exe -r Resources -user true model.xml

neo.exe -r Resources -support true -o Base model.xml

The first command generates SQL queries and puts them into Sql folder. Using these queries you may create database for sample application. The second command generates model classes that can be modified by the user (will not be regenerated by NEO in future). The third command creates base model classes that will be regenerated by NEO when the model is changed. Finally, we will have the structure as shown in the picture below:

After BAT file execution, we should include new classes into our project and change the namespaces.

Now we should create a database and execute the generated model.sql script from the Sql folder.

Connecting to database

Let's do the last part of preparations - connecting to database. First of all, create DataStoreFactory class that will create database connections. There will be two parameters in Web.config.

<add key="ConnectionString" 
  value="data source=(local);initial catalog=ormapping;uid=sa;pwd=" />
<add key="DatabaseServer" value="Neo.SqlClient.SqlDataStore,Neo" />

DataStoreFactory has just one Create method:

public IDataStore Create()
{    
    
    string className = 
        ConfigurationSettings.AppSettings["DatabaseServer"];

    string conn = 
        ConfigurationSettings.AppSettings["ConnectionString"];

    return (IDataStore)Activator.CreateInstance(
    Type.GetType(className + ",Neo"),new object[] {conn});
}

It's time to have fun with NEO framework. The first application's page will add a new project and show the project's list. Oh, we did not finish the connection operation. OK, that's easy. Just instantiate IDataStore and create an instance of ObjectContext. Actually, ObjecyContext is very interesting and it is a unit of work pattern. It stores all the changes of loaded objects and is mandatory for all operations with NEO.

// create data store

DataStoreFactory store = new DataStoreFactory();
IDataStore dataStore = store.Create();

// create context for changes tracking

context = new ObjectContext(dataStore);

Persistence

There is a web form on the page, as usual, with several text boxes and a button. When the user fills in the form and pushes the button, a new project is added into the database. The following event handler will do all the actions:

private void btnAddProject_Click(object sender, System.EventArgs e)
{
    // create new project

    ProjectFactory projectFactory = new ProjectFactory(context);
    Project project = projectFactory.CreateObject();

    // set project properties

    project.Name = txtName.Text;
    project.Description = txtDescription.Text;
    project.StartDate = Convert.ToDateTime(txtStartDate.Text);

    // save new project into the database

    context.SaveChanges();
}

First the ProjectFactory class is instantiated. This class is responsible for all operations with the projects: create, retrieve, delete and so on. Then we create a new project. Note that this new project is not there in the database right now. In the next step we set all the project properties from the form's fields. And, finally, we save all the changes in ObjectContext into the database. In our case, NEO will generate the INSERT query.

Now it is required to get all the projects from the database and show them in a list. The code in Page_Load handler is very simple:

private void Page_Load(object sender, System.EventArgs e)
{
    // create data store

    DataStoreFactory store = new DataStoreFactory();
    IDataStore dataStore = store.Create();

    // create context for changes tracking

    context = new ObjectContext(dataStore);
            
    // retrieve all projects from database

    ProjectList projects = new ProjectFactory(context).FindAllObjects();

    // fill datagrid

    lstProjects.DataSource = projects;
    lstProjects.DataBind();
}

Again, we use ProjectFactory to retrieve all the projects from the database and use the list as a DataSource for lstProjects DataGrid. That's it.

Conclusion

Do you remember the time when dynamic SQL or stored procedures were used for database operations? NEO or any other good O/R mapping solution makes life easier and code lighter. A lot!

That was just a quick start with NEO. In the next article we will check more complex topics, like complex queries, possible architectural solutions for ObjectContext handling, detached database and TDD with NEO, inheritance problem and so on.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here