Tuesday, February 27, 2007

NHibernate reference app

Billy McCafferty has created a really nice NHibernate sample app on CodeProject using good ol’ Northwind. If you’re interested in NHibernate, you ought to take a look at it. Billy watches user comments on the article, and gives good help with questions.

His project shows a really nice example of using NHibernate.Generics, and creates a many-to-many bidirectional relationship between Customer and Order. I was interested in extending his domain just to play around a bit, so I decided to create an OrderDetail class. Specifically, I wanted to create a one-to-many one-way relationship from Order to OrderDetail – in other words, I just want the Order to keep track of its own OrderDetails, and I don’t think I really care about being able to see the Order from an OrderDetail. In order to get that working, I had to do the following:

1) Change the OrderDetails table definition in Northwind to include an integer field called OrderDetailID, and make that field the primary key.

2) Create an OrderDetail class like so:


using System;
using NHibernate.Generics;

namespace NHibernateSample.Core.Domain
{
public class OrderDetail : DomainObject
{
public OrderDetail()
{
}

public int ProductId
{
get { return productId; }
set { productId = value; }
}

public decimal UnitPrice
{
get { return unitPrice; }
set { unitPrice = value; }
}

public int Quantity
{
get { return quantity; }
set { quantity = value; }
}

public float Discount
{
get { return discount; }
set { discount = value; }
}


private int productId;
private decimal unitPrice;
private int quantity;
private float discount;

}
}

3) Add an OrderDetails collection to the Order class:

public IList<OrderDetail> OrderDetails
{
get { return _orderDetails; }
}
.

.
.
private EntityList<OrderDetail> _orderDetails = new EntityList<OrderDetail>();


4) And then map everything:
---- order.hbm.xml ----


<bag name="OrderDetails" lazy="true" table="OrderDetails" inverse="false"
access="NHibernate.Generics.GenericAccessor, NHibernate.Generics" >
<key column="OrderID" />
<one-to-many class="NHibernateSample.Core.Domain.OrderDetail, NHibernateSample.Core" />
</bag>


---- orderdetail.hbm.xml ----

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.0">
<class name="NHibernateSample.Core.Domain.OrderDetail, NHibernateSample.Core" table="OrderDetails">
<id name="ID" column="OrderDetailsID" unsaved-value="0">
<generator class="identity" />
</id>


<property name="ProductId" column="ProductID" />
<property name="UnitPrice" column="UnitPrice" />
<property name="Quantity" column="Quantity" />
<property name="Discount" column="Discount" />


</class>
</hibernate-mapping>


One thing that threw me initially was the WireUpEntities() method he calls in the Customer and Order classes. Here's the one he has in the Customer class:

private void WireUpEntities() {
// Implement parent/child relationship add/remove scaffolding between Customer and Orders
_orders = new EntityList<Order>(
delegate(Order order) { order.OrderedBy = this; },
delegate(Order order) { order.OrderedBy = null; }
);
}


Since I’ve never used NHibernate generics, I apparently decided it would be a good idea to put my brain out to pasture for a little while, and not to actually dig in and understand what’s going on here before trying to use it for my OrderDetails collection. I tried some goofy similar stuff in the OrderDetails class, but it turns out that this code is for enforcing the bi-directional relationship – not needed at all for one-way relationships, I can just relax and let NHibernate do the work for me. The only difference is that I have to do this in my Order class:

private EntityList<OrderDetail> _orderDetails = new EntityList<OrderDetail>();

instead of this:

private EntityList<OrderDetail> _orderDetails;

In the bi-directional relationship between Customer and Order, WireUpEntities() instantiates the EntityList, but since you don’t need WireUpEntities() for one-way relationships you have to instantiate the EntityList yourself. NHibernate will only work with an existing list...it won't initialize _orderDetails to a new EntityList itself. So if you don't call "new" in a wire-up method, then it needs to be done when declaring the member.

No comments: