NHibernate, Part II

Posted by Jack Altiere on June 29th, 2008

In my last article, I decided to give NHibernate a try to try and get my mind around what an ORM brings to the table.  I took a few hours and managed to get a simple example working, and was able to read and write data to the AdventureWorks database.   My example was simple though, now I would like to try and dig a little deeper into NHibernate.

The first topic that I would like to delve into is relationships.  In my last article, I had a Contact business entity, but I was pulling all of the data for this object from a single database table.  This is often not the case in the real world, especially in situations where you don’t have absolute control over the database schema.  I would like to expand my example and add a new class called Employee that is derived from the Contact class.  I’ll just pick a few fields from the HumanResources.Employee table in AdventureWorks for this class, making my class diagram look like this:

If I were writing a stored procedure this would be a walk in the park for me, I would just join the relevant tables in the database with an inner join and populate the Employee record with the results.  The SQL query would look something like this:

select e.EmployeeID, e.Title, e.HireDate, e.SalariedFlag, c.FirstName, 
c.MiddleName, c.LastName, c.EmailAddress, c.ModifiedDate
from HumanResources.Employee e
inner join Person.Contact c on e.ContactID = c.ContactID
where e.ContactID = @ID

Let’s figure out how to do the same task using NHibernate.  I’ll admit, it took me some trial and error to find a way to do this.  The learning curve to pick up all of the nuances for class mapping is a little steep.  I started by trying to create a mapping file for my Employee class, but I was hitting a wall because of how I set up the inheritance.  I’m fairly sure I could have gotten it to work that way if I had the Employee class encapsulate the Contact class rather than inheriting from it.  I ended up getting this to work by editing the mapping file for the Contact class, which was a little non-intuitive to me.  My new mapping file looks like this:

   1:  <?xml version="1.0" encoding="utf-8" ?>
   2:  <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
   3:    <class name="NHibernateSample.Contact, NHibernateSample" 
   4:                                                               table="Person.Contact">
   5:      <id name="ContactID" type="Int32">
   6:        <generator class="assigned" />
   7:      </id>
   8:      <property name="FirstName" type="String" length="50"/>
   9:      <property name="MiddleName" type="String" length="50"/>
  10:      <property name="LastName" type="String" length="50"/>
  11:      <property name="Email" column="EmailAddress" type="String" length="50"/>
  12:      <property name="LastModified" column="ModifiedDate" type="DateTime"/>
  13:   
  14:      <joined-subclass name="NHibernateSample.Employee,
  15:         NHibernateSample" table="HumanResources.Employee">
  16:        <key column="ContactID"/>
  17:        <property name="EmployeeID" type="Int32"/>
  18:        <property name="Title" type="String" length="50"/>
  19:        <property name="HireDate" type="DateTime"/>
  20:        <property name="SalaryFlag" column="SalariedFlag" type="Boolean"/>
  21:        <property name="LastModified" column="ModifiedDate" type="DateTime"/>
  22:      </joined-subclass>
  23:      
  24:    </class>
  25:  </hibernate-mapping>

I added the joined-subclass section to the mapping file, which tells NHibernate how to load the information that is specific to the employee.  I loaded a random employee from the database (ContactID = 1001) and I was able to return the correct data.

I was worried that this solution would break previous examples.  In the AdventureWorks schema, not all contacts are employees, which is consistent with our data model.  Since I modified the mapping file for the Contact class, I thought I might have broken things in the situation where I load a Contact record that wasn’t actually an employee.  NHibernate handled this situation perfectly, and I was able to load the contact record successfully.

Next on my list of things to try was to get a one to many relationship to work.  The logical place for me to test this out is in the ordering system.  I want create an Order class, and in this class I want to store a list of order details that make up the order.  To add a little more complication, I made the OrderDetail class encapsulate a Product class, making my final class structure look like this:

   1:  public class Order
   2:  {
   3:     private int _orderID;
   4:     private DateTime _orderDate;
   5:     private decimal _orderTotal;
   6:     private IList _lineItems;
   7:   
   8:     public virtual int OrderID
   9:     {
  10:        get { return _orderID; }
  11:        set { _orderID = value; }
  12:     }
  13:     public virtual DateTime OrderDate
  14:     {
  15:        get { return _orderDate; }
  16:        set { _orderDate = value; }
  17:     }
  18:     public virtual decimal OrderTotal
  19:     {
  20:        get { return _orderTotal; }
  21:        set { _orderTotal = value; }
  22:     }
  23:     public virtual IList LineItems
  24:     {
  25:        get { return _lineItems; }
  26:        set { _lineItems = value; }
  27:     }
  28:   
  29:     public Order()
  30:     {
  31:   
  32:     }
  33:  }
  34:   
  35:  public class OrderDetail
  36:  {
  37:     private int _orderDetailID;
  38:     private int _quantity;
  39:     private string _trackingNumber;
  40:     private decimal _price;
  41:     private Product _item;
  42:   
  43:     public virtual int OrderDetailID
  44:     {
  45:        get { return _orderDetailID; }
  46:        set { _orderDetailID = value; }
  47:     }
  48:     public virtual int Quantity
  49:     {
  50:        get { return _quantity; }
  51:        set { _quantity = value; }
  52:     }
  53:     public virtual string TrackingNumber
  54:     {
  55:        get { return _trackingNumber; }
  56:        set { _trackingNumber = value; }
  57:     }
  58:     public virtual decimal Price
  59:     {
  60:        get { return _price; }
  61:        set { _price = value; }
  62:     }
  63:     public virtual Product Item
  64:     {
  65:        get { return _item; }
  66:        set { _item = value; }
  67:     }
  68:   
  69:     public OrderDetail()
  70:     {
  71:   
  72:     }
  73:  }
  74:   
  75:  public class Product
  76:  {
  77:     private int _productID;
  78:     private string _name;
  79:     private string _productNumber;
  80:   
  81:     public virtual int ProductID
  82:     {
  83:        get { return _productID; }
  84:        set { _productID = value; }
  85:     }
  86:     public virtual string Name
  87:     {
  88:        get { return _name; }
  89:        set { _name = value; }
  90:     }
  91:     public virtual string ProductNumber
  92:     {
  93:        get { return _productNumber; }
  94:        set { _productNumber = value; }
  95:     }
  96:   
  97:     public Product()
  98:     {
  99:   
 100:     }
 101:  }

You’ll notice on lines 6 and 23 above that my collection of OrderDetail objects is stored as an IList.  Normally I would use a List<OrderDetail> in this situation, but I couldn’t get NHibernate to map this correctly, so I used an IList instead.  The next step is to come up with the NHibernate mapping files.  A little reminder, make sure you change your .hbm.xml files to be embedded resources in the build action in Visual Studio. I forgot to do this and NHIbernate throws an exception and will be unable to load the class.  I created a mapping file for each of my 3 new classes, and they look like this:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
  <class name="NHibernateSample.Order, NHibernateSample"
                                table="Sales.SalesOrderHeader">
    <id name="OrderID" column="SalesOrderID" type="Int32">
      <generator class="assigned" />
    </id>
    <property name="OrderDate" type="DateTime"/>
    <property name="OrderTotal" column="TotalDue"
                               type="Decimal"/>

    <bag name="LineItems" inverse="true">
      <key column="SalesOrderID"/>
      <one-to-many
           class="NHibernateSample.OrderDetail, NHibernateSample"/>
    </bag>

  </class>
</hibernate-mapping>

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
  <class name="NHibernateSample.OrderDetail, NHibernateSample"
                                  table="Sales.SalesOrderDetail">
    <id name="OrderDetailID" column="SalesOrderDetailID"  type="Int32">
      <generator class="assigned" />
    </id>
    <property name="Quantity" column="OrderQty"  type="Int32"/>
    <property name="TrackingNumber" column="CarrierTrackingNumber"
                                         type="String" length="25"/>
    <property name="Price" column="UnitPrice" type="Decimal"/>

    <one-to-one name="Item"
            class="NHibernateSample.Product, NHibernateSample"/>

  </class>
</hibernate-mapping>

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
  <class name="NHibernateSample.Product, NHibernateSample"
                      table="Production.Product">
    <id name="ProductID" type="Int32">
      <generator class="assigned" />
    </id>
    <property name="Name" type="String" length="50"/>
    <property name="ProductNumber" type="String" length="25"/>
  </class>
</hibernate-mapping>

There are several new additions to these mapping files. First, in the Order class, I have a bag construct, which specifies a one to many relationship to OrderDetail.  A bag means that I have a collection of objects, and is mapped to the IList in my class definition.  The one-to-many construct also makes sense, because 1 order has many line items.  In the OrderDetail class, we have a one-to-one relationship defined to the Product class.  This is also intuitive when you think about it, because a single line item represents 1 product.  The quantity purchased can be more than one, but we’re still talking about a single product.

To test this out, I created a method in my data layer to load an order based on the order ID which looks like this:

   1:  public static Order LoadOrderById(int orderID)
   2:  {
   3:     Configuration cfg = LoadConfig();
   4:     ISessionFactory factory = cfg.BuildSessionFactory();
   5:     ISession session = factory.OpenSession();
   6:   
   7:     Order current = (Order)session.Load(typeof(Order), orderID);
   8:   
   9:     return current;
  10:  }

I plugged in a valid order ID from the database, and everything loaded perfectly! I’m starting to see the power of NHIbernate, although mapping relationships can be a little tough to grasp at first.  When learning how to map classes, I found the NHibernate site to be an invaluable resource, so be sure to check it out.  Next time I will take ActiveRecord for a spin and see if it makes the process of mapping classes any easier.

kick it on DotNetKicks.com

NHibernate Experiment

Posted by Jack Altiere on June 26th, 2008

Rather than just start another argumentative post about whether stored procedures are better than using an ORM, I’m just going to give NHibernate a try for myself.  If you want a good breakdown of the argument, you can check out posts by Jeff Atwood, Eric Wise or Rob Howard.  If any of those articles don’t do it for you, just run a Google search on the topic…it has been debated about as much as the age old question of whether the chicken or the egg came first.

I have a secret  to admit…….I’ve never used an ORM before.  At my current job, we use stored procedures for all data access so I’ve never really had a reason to use them.  It’s always been one of those things on my “to do” list of stuff to check out that I never quite get to.  Today is the day to cross this one off the list! I’m going to approach this article differently than anything else I’ve written.  Usually I write about something I know or I’ve used, this time I’m going to write as I go.   I’m sure I’ll stub my toe along the way, and this will probably have to be broken up into multiple articles, but it should be an interesting journey.

I decided to try version 1.2.1 of NHibernate, even though I saw a 2.0 version available in alpha.  For a database, I wanted something with a lot of data in it that was available to anyone that actually wanted to follow along, so I decided to use everyone’s favorite database……….Adventureworks!

The first thing I want to do is create a class to represent some business entity.  I chose a simple example and created a Contact class, and I simply chose a few fields that exist in an AdventureWorks table called Person.Contact.  I’m starting simple because I’m learning as I go, so I’m not going to worry about how to include data in my class that would be populated via a joined query in a stored procedure.   I hope to get more complicated later, but I just want to get a feel for things first.

When I first created my class, I ran into an error that required me to make all of my public properties virtual.  When I made that change, my contact class ended up looking like this: 

   1:  public class Contact
   2:  {
   3:     private int _contactID;
   4:     private string _firstName;
   5:     private string _middleName;
   6:     private string _lastName;
   7:     private string _email;
   8:     private DateTime _lastModified;
   9:   
  10:     public virtual int ContactID
  11:     {
  12:        get { return _contactID; }
  13:        set { _contactID = value; }
  14:     }
  15:     public virtual string FirstName
  16:     {
  17:        get { return _firstName; }
  18:        set { _firstName = value; }
  19:     }
  20:     public virtual string MiddleName
  21:     {
  22:        get { return _middleName; }
  23:        set { _middleName = value; }
  24:     }
  25:     public virtual string LastName
  26:     {
  27:        get { return _lastName; }
  28:        set { _lastName = value; }
  29:     }
  30:     public virtual string Email
  31:     {
  32:        get { return _email; }
  33:        set { _email = value; }
  34:     }
  35:     public virtual DateTime LastModified
  36:     {
  37:        get { return _lastModified; }
  38:        set { _lastModified = value; }
  39:     }
  40:   
  41:     public Contact()
  42:     {
  43:   
  44:     }
  45:  }

The next thing I need to do is set up an XML file that maps my newly created class to the database table.  This is the crux of how NHibernate works, and it uses these mappings to load or change data in the database.  You will need one of these mapping files for every business entity that you are using.  I’m following instructions that I came across here when creating this file, and it ended up looking like this: 

   1:  <?xml version="1.0" encoding="utf-8" ?>
   2:  <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
   3:    <class name="NHibernateSample.Contact, NHibernateSample" table="Person.Contact">
   4:      <id name="ContactID" type="Int32">
   5:        <generator class="assigned" />
   6:      </id>
   7:      <property name="FirstName" type="String" length="50"/>
   8:      <property name="MiddleName" type="String" length="50"/>
   9:      <property name="LastName" type="String" length="50"/>
  10:      <property name="Email" column="EmailAddress" type="String" length="50"/>
  11:      <property name="LastModified" column="ModifiedDate" type="DateTime"/>
  12:    </class>
  13:  </hibernate-mapping>

I did run into a few snags when coming up with this mapping file.  The first is that the example I was reading used the xmlns of “urn:nhibernate-mapping-2.0″, and this was causing exceptions in the code to be thrown.  After some digging, I found that changing this to 2.2 fixed the problem. In case you run into the same problem, the exception message I got was “NHibernateSample.Contact.hbm.xml(2,2): XML validation error: Could not find schema information for the element ‘urn:nhibernate-mapping-2.0:hibernate-mapping’.“ 

In the XML file, you only have to specify the column if it differs from the property name, so in my example I needed to specify the column names for the Email and LastModified properties. I read that the type attribute is optional and that NHibernate will use reflection to figure it out if you leave it out, but it seems more efficient to me just to specify it now.  NHibernate needs to know what the primary key is for the table in question, and I specified this in the id section.  In my case, I told NHibernate that I would be assigning my own keys on line 5, where it says: generator class=”assigned”.  I also set the build action of the .xml file in my Visual Studio solution to be an embedded resource. 

The next step is to tell NHibernate how to get to my database.  I accomplished this by adding a section for NHibernate in my app.config file.  I believe you also have the option of creating a separate config file if you want rather than using the app.config (or web.config, depending on how you are using it) but I didn’t try it myself.  My app.config file ended up looking like this:

   1:  <?xml version="1.0" encoding="utf-8" ?>
   2:  <configuration>
   3:   
   4:    <configSections>
   5:      <section
   6:        name="nhibernate"
   7:        type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
   8:      />
   9:    </configSections>
  10:    
  11:    <nhibernate>
  12:      <add
  13:        key="hibernate.connection.provider"
  14:        value="NHibernate.Connection.DriverConnectionProvider"
  15:      />
  16:      <add
  17:        key="hibernate.dialect"
  18:        value="NHibernate.Dialect.MsSql2005Dialect"
  19:      />
  20:      <add
  21:        key="hibernate.connection.driver_class"
  22:        value="NHibernate.Driver.SqlClientDriver"
  23:      />
  24:      <add
  25:        key="hibernate.connection.connection_string"
  26:        value="Server=localhost;initial catalog=AdventureWorks;Integrated Security=SSPI"
  27:      />
  28:    </nhibernate>
  29:    
  30:  </configuration>

I’m sure there are lots of tuning options available here, but at this point I’m interested in getting it up and running.  I have to say, setting up all this configuration in XML files can be a little tedious!

At this point, I have everything configured correctly (I think) and I’m ready to actually try to pull some data out.  I guess the first thing I want to try is to pull a contact record out of the table based on the ID.  I put together this code to retrieve a contact by its ID, made my data layer look like this:

   1:  using NHibernate;
   2:  using NHibernate.Cfg;
   3:  using NHibernate.Expression;
   4:   
   5:  namespace NHibernateSample
   6:  {
   7:     public class DataLayer
   8:     {
   9:        public static Contact LoadContactById(int contactID)
  10:        {
  11:           Configuration cfg = LoadConfig();
  12:           ISessionFactory factory = cfg.BuildSessionFactory();
  13:           ISession session = factory.OpenSession();
  14:   
  15:           Contact current = (Contact)session.Load(typeof(Contact), contactID);
  16:   
  17:           return current;
  18:        }
  19:   
  20:        private static Configuration LoadConfig()
  21:        {
  22:           Configuration cfg = new Configuration();
  23:           cfg.AddAssembly("NHibernateSample");
  24:           
  25:           return cfg;
  26:        }
  27:   
  28:     }
  29:  }

 
What I’m doing is loading my configuration, creating an ISessionFactory from this configuration, and then loading my object based on the mappings I set up.   You can find some good examples of how this works here.  Lastly, to test all of this I set up a console application, and added a reference to my library to it.  The code in my console application just calls this function and prints out the results, it looks like this:

   1:  class Program
   2:  {
   3:     static void Main(string[] args)
   4:     {
   5:        Contact ct = DataLayer.LoadContactById(24);
   6:        StringBuilder sb = new StringBuilder();
   7:        sb.AppendLine("First Name: " + ct.FirstName);
   8:        sb.AppendLine("Middle Name: " + ct.MiddleName);
   9:        sb.AppendLine("Last Name: " + ct.LastName);
  10:        sb.AppendLine("Email: " + ct.Email);
  11:        Console.WriteLine(sb.ToString());
  12:   
  13:        Console.ReadLine();
  14:     }
  15:  }

To make sure this is working correctly, I took a peek in the table with SQL Management Studio and saw that with the ID I picked out, I should get a record back for the contact Sean P. Jacobson, and when I run that I see that this is in fact the case.

Success!  Granted, this was a simple example, but I was able to query data from the database without writing a line of SQL.  What happens if I want to change Sean’s last name?  By simply adding this method to my data layer I have full save capability. 

   1:  public static void SaveContact(Contact contact)
   2:  {
   3:     Configuration cfg = LoadConfig();
   4:     ISessionFactory factory = cfg.BuildSessionFactory();
   5:     ISession session = factory.OpenSession();
   6:     ITransaction transaction = session.BeginTransaction();
   7:   
   8:     Contact temp = (Contact)session.Load(typeof(Contact), contact.ContactID);
   9:     temp.FirstName = contact.FirstName;
  10:     temp.MiddleName = contact.MiddleName;
  11:     temp.LastName = contact.LastName;
  12:     temp.Email = contact.Email;
  13:   
  14:     session.Flush();
  15:     transaction.Commit();
  16:     session.Close();
  17:  }
 

To test this out, I added the code below to my console application after I had the contact record loaded and the update was successfully committed to the database.  I made this update an atomic operation using the ITransaction interface, and flushed the session to write the changes back to the database.

   1:  ct.MiddleName = "Humperdinkel";
   2:  DataLayer.SaveContact(ct);
 

The last thing I want to accomplish in my first NHibernate session is to load a group of contacts with a query.  I want to pass in a string, and load any contacts whose first name or last name contain those letters.  This is done using Expressions, which turned out to be fairly straight forward.  I added this class to my data layer:

   1:  public static IList ContactSearch(string search)
   2:  {
   3:     Configuration cfg = LoadConfig();
   4:     ISessionFactory factory = cfg.BuildSessionFactory();
   5:     ISession session = factory.OpenSession();
   6:   
   7:     IList contacts = session.CreateCriteria(typeof(Contact))
   8:        .Add(Expression.Or(Expression.Like("LastName", "%" + search + "%"),
   9:                       Expression.Like("FirstName", "%" + search + "%")))
  10:        .List();
  11:   
  12:     return contacts;
  13:  }

Then by adding this block of code to my console application, I was able to query for a list of any contact in the database that had the text ‘Joe’ in their first name or last name.

   1:  IList contacts = DataLayer.ContactSearch("Joe");
   2:   
   3:  if (contacts.Count == 0)
   4:     Console.WriteLine("No contacts match.");
   5:  else
   6:  {
   7:     Console.WriteLine(contacts.Count.ToString() + " matching contact(s):");
   8:     foreach (Contact current in contacts)
   9:        Console.WriteLine(current.FirstName + " " + current.LastName);
  10:  }

Running the console app prints a list of 67 names, which is the same number I got back when I wrote the SQL by hand to test things out.   I was successfully able to use NHibernate to query the AdventureWorks database, and I was able to save data back to the database…..all without writing a line of SQL. 

In my next article, I will try to get a little more complex and explore relationships.  Up until now, my examples have been simple….the business objects got their data from a single table.  I want to expand this to capture data for a business entity from multiple tables.  I will eventually dig into how Castle’s mapping pattern project ActiveRecord works with NHibernate, and I will determine if we can get rid of some of the XML configuration pain I ran into.

kick it on DotNetKicks.com

Downloads

Twitter Updates

    Top Commentators

    • No commentators.

    Copyright © 2007 Jack Altiere. All rights reserved.