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"/> </set>
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:
someCustomer.Orders.Add(someNewOrder); ...query the db using HQL ... someNewOrder.OrderDate = DateTime.Now; .... CustomerRepository.SaveOrUpdate(someCustomer); transaction.Commit();
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:
"UPDATE ORDERS SET ...CUSTOMER_ID=NULL... WHERE...".
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
update="false"/>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; OrderRepository.SaveOrUpdate(someOrder);it won't work if you chose option No. 3...