Just a quick post to share some learnings I've had around a good ol' NHibernate "Object references an unsaved transient instance" error message. In essence, this message is telling you that you're trying to save an object which has a reference to another unsaved object, and NHibernate for one reason or another is unable to work out which object to save first so that it can work out the ID to apply to the other end of the reference. What went wrong and how did I fix it?
First off, thanks to the author of http://cagataycivici.wordpress.com/2005/11/15/p_i_ve_faced_with/ for giving me my initial steer with this. Now on to my specific example.
Some Background - What Got Me Into This Mess
When we use NHibernate here we've got a single object graph that we pass around and manipulate parts of. This object graph is a collection of mappings that have references between one-another, to represent the foriegn key relationships in your database. For simplicity sake, let's say our object looks something like the following:
Order - Order
Order.Items - IList<Item>
Order.Items.DeliveryStatusAudit - IList<DeliveryStatusAudit>
This is a collection of three objects (Order, Item and DeliveryStatusAudit) with three corresponding mappings to three corresponding database tables. When we work with our Order we let NHibernate manage the loading and saving of the properties, sub-objects, sub-properties, etc. But that there DeliveryStatusAudit property hanging off of the Item is something different. Because this is an audit list, we don't want to load this data most of the time. So our mapping files look like the following:
<class name="Order" table="tbl_Order">
<id name="Id" column="orderId" />
<key column="itemId" />
<one-to-many class="Item" />
<class name="Item" table="tbl_Item">
<id name="Id" column="itemId" />
<many-to-one name="SalesOrder" class="Order" cascade="save-update" column="orderId" />
<class name="DeliveryStatusAudit" table="tbl_DeliveryStatusAudit">
<id name="Id" column="itemId" />
<many-to-one name="Item" class="Item" column="itemId" />
There are two important points to note here:
- Our Order has a "bag" of Items, but our Item mapping does not have a bag of DeliveryStatusAudit classes. This is not the cause of our problem, but hints that there's something different about the relationship with the DeliveryStatusAudit class.
- The many-to-one relationship "Item -> Order" states a "cascade" property, while with our "DeliveryStatusAudit -> Item" relationship does not. This is the root cause of the problem, which we either work around or we add this property.
The Two Possible Fixes
Options 1 - Add a "cascade" property to your relationship. If you have a many-to-one relationship between two classes, if you have a cascade on that relationship, an attempt to save an object on the "many" side will automatically save the "one" and any associated classes by spidering through the graph. This may not be good news if you have a small object referencing a much bigger graph, as in our case!
Option 2 - Save Things in the Right Order. If you do not have a cascade on this relationship, and you try and save a Many before you've saved the One (a DeliveryStatusAudit record before you've saved the Item), NHibernate will not do anything clever for you, you'll get a lovely "Object references an unsaved transient instance" error. I'm guessing that your DeliveryStatusAudit record will have a null ItemId property, which your database will of course expect to be a FK referring to a valid existing Item.