Sunday, November 13, 2011

NHibernate bi-directional - use it carefully (Part II)

In the previous post I've showed you one reason to be very careful when using bi-directional associations in NHibernate.

In this post I'll show you another reason. Again, for the example, I will use Customer with many Orders.

We will create a one-to-many association from Customer to Orders:
<set name="Orders" lazy="true" cascade="all-delete-orphan">
      <key column="CUSTOMER_ID" />
      <one-to-many class="Order" not-found="ignore"/>

and we will create a many-to-one from Order to Customer:
<many-to-one name="Customer" class="Customer" column="CUSTOMER_ID"/>
Now consider the following scenario:

...query the db using HQL


someNewOrder.OrderDate = DateTime.Now;



If you'll take a look at table 'ORDERS' right after the commit, you'll find out that the CUSTOMER_ID field in the new record created for someNewOrder is... NULL.

And why is that?
let's see what NHibernate is doing a step by step:
1 - Right before querying the DB by using HQL, NHibernate is flushing all the changes to the DB, and that includes creating a new record for someNewOrder At this stage the record in the DB is perfectly well.

2 - By changing someNewOrder.OrderDate NHIbernate considers someNewOrder as 'dirty' and it will flushed it to the DB later on when the transaction will be commited.

3 - Finally, when the transaction is commited, again NHibernate is flushing all the changes but this time it's generating an 'UPDATE' statement for someNewOrder since it's no longer a new object - it has a record in the DB.

And here is the problem - as we saw in the last step, an 'UPDATE' statement for someNewOrder
is executed. But since someNewOrder.Customer is null (nowhere in the code we've ever changed it) - the 'UPDATE' statement looks something like this:

OK, now you have a few options to solve it:
1 - do not use HQL - use session.Get() instead (only if you can...). This doesn't enforce NHibernate to flush like HQL does.

2 - You can make sure that someNewOrder.Customer actually does hold the Customer before committing.

3 - You can go to Order.hbm.xml and add update="false" to the many-to-one association. like this:
<many-to-one name="Customer" class="Customer" column="CUSTOMER_ID 
this tells NHibernate not to include the Customer when generating an 'UPDATE' statement for an Order.
But this also means that you can not update the Order's Customer like this:
someOrder.Customer = someCustomer;
it won't work if you chose option No. 3...

No comments:

Post a Comment