NHibernate, Part II
Software Development June 29th, 2008In 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.
July 9th, 2008 at 9:25 am
Hello… great article!!!
I need some help… I have a class Client with various properties like phone, name, address etc…
I also have a User class which I want to inherit from Client… so the db tables have a unique id for Client and User. The User tbl also has a ClientId which helps connect them…
How do I make such inheritance in NHibernate???
Thank you very much!
July 9th, 2008 at 9:47 am
If I understand correctly, it sounds like you have the same sort of inheritance that I used in my example. I think that you want to put a joined-subclass entry in your Client mapping file, similiar to what I did above in my Contact class.
July 10th, 2008 at 7:25 am
Yes I tried that.. but what happens when you want to update the fields of the User ??? I did it and the query that is produced is like … ‘update User set… where ClientId=?’.
But the ClientId is not the unique id of the User table (the unique id is another field which I call UserId).
I see that NHibernate does the update based on the key that connects these 2 classes, the ClientId…
I dont know if I make sense…
January 13th, 2009 at 6:12 pm
http://www.koloskmv.ru/
April 23rd, 2009 at 7:41 am
Hello,
I would like to take to this opportunity to ask a question about one-to-many. I ahve two classes:
GeneralInformation and famousPlacesInLondon
public class GeneralInformation
{
private int id;
public virtual int Id
{
get { return id; }
set { id = value; }
}
private IList famousPlacesOutLondon;
public virtual IList FamousPlacesOutLondon
{
get { return famousPlacesOutLondon; }
set { famousPlacesOutLondon = value; }
}
}
public class FamousPlacesInLondon
{
private int id;
public virtual int Id
{
get { return id; }
set { id = value; }
}
private string link;
public virtual string Link
{
get { return link; }
set { link = value; }
}
private string title;
public virtual string Title
{
get { return title; }
set { title = value; }
}
}
}
My mapping are as follows:
If I fill the GeneralInformation with data and do a save using NHibernate, everything gets inserted into the appropriate table, except the ForeignKey GeneralInformationId. What is wrong?
TIA
Yaz