When working with parent/child relationships in object models it is important to know what kind of Inverse Management your ORM technology have. Inverse management means handling all the relationships and keys shared between the parent and the child. This post will help you understand how NHibernate manages these relationships and what options you have.
Standard parent / child with inverted properties
The standard parent / child object model usually looks something like the below picture;
figure 1, Standard parent / child
In figure 1 you see that the comment entity has an inverse property back to product.
Note: NHibernate requires you to manually set the product property on the comment to the correct object, it has no automatic inverse management of properties. This is usually done by adding an “AddComment” method on the product that encapsulate the logic needed to get the relationship right.
The below represents how the foreign key constraint in the database looks. Figure 2 shows the standard parent / child;
figure 2, parent child database model
In this case the inverted property ensures that the comment object itself will “contain” a copy of the product Id to insert into the database. You tell NHibernate about this relationship and how to handle the keys by setting up the mapping like listing 1;
<class name="Product" table="Products">
<bag name="Comments" inverse="true" cascade="all">
<key column="ProductId" />
<class name="Comment" table="Comments">
<many-to-one name="Product" column="productId" />
listing 1, standard parent / child mapping
Using the above xml NHibernate will have enough to figure out that the product id should be persisted into the comments table together with the comment itself.
The bag mapping tells the product that there is an inverse property on the comment and instructs NHibernate to let the comment handle the relationship on it’s own.
Variation 1, no inverse property on the child
A common approach in object modeling is to use aggregate roots and just let the relationship flow from the parent to the child, not an inverse back. This makes sense when you think about the object behaviors; comment will never stand on its own, it will always be accessed through the product.
Figure 3 illustrates how the such a model looks like;
figure 3, Aggregate model
This approach leaves NHibernate a bit dry. In this variation; comment can’t stand on its own and will not be able to deliver the product id to the database. It will instead rely on the comments list from the product to provide that. NHibernate needs to be told that this is your intention the bag declaration has to be changed into;
<bag name="Comments" inverse="false" cascade="all">
NHibernate now knows that the comment entity doesn’t have a parent property that points back.
There is a caveat with this though, NHibernate waits a bit to insert the identity of the product into the comment. Figure 4 shows the statements NHibernate sends to the database;
figure 4, Statements sent to the database
As you can see, the product id is sent in a separate statement after the rows have been inserted. This means that the product Id column in the comments table has to be nullable. As long as this save will be in a transaction and the amount for rows are small, this will be a viable solution. Just be aware of the mechanics NHibernate uses.
Variation 2, the hybrid solution
If you don’t want the inverse property and can’t set the foreign key to nullable the two above solutions won’t help you. For this variation you need to put a hybrid solution together.
This is a similar to the standard parent / child, but instead of the full entity we will only use a protected field on the comment. The field you want to add would look something like the following;
protected int _MAP_productId;
which then would be mapped like a regular property, not an object reference;
<property name="_MAP_productId" access="field" />
Note: It’s usually a very good idea to name the field with an awkward name like the one above, this ensures that developers after you will think twice before using it for any other purpose then mapping. This is also a place where I would consider adding a code comment.
To set the field you could either create a constructor or expose an internal property that the product can use. Don’t try to write to the field from the outside directly, NHibernate has issues with internal fields and making it public will just be ugly.
The drawback with this approach is that NHibernate won’t be able to automatically set the identity on the comment. This means that you have one of two options for getting that product id:
- Don’t use auto-generated Id’s, make sure you assign one to the product before adding any comments.
- Save the product first, before adding any comments to it. This way the Id will be set in time.
I’m sure there is an extension point somewhere in NHibernate that would allow for the above variation to be automatically handled. I will get back to you when and if I find it.
The object model and relational model are different schemas and as such compromises have to happen. NHibernate makes a very good job in hiding those compromises in most cases, but when it comes to inverse management you the developer need to take a stand on what compromise is the right one for your solution. Now you know your options, choose wisely.
Nhibernate project website:
NHibernate documentation about parent / child:
NHProf application by Ayende that was used to inspect the queries sent: