In a previous post, I talked about cascading deletes being a new feature introduced by NHibernate 2.0. If you haven't heard about this before, then you'd probably be interested to read about it first.
Cascading deletes are all great if your database of choice supports CASCADE DELETE foreign key constraints. But what if it doesn't provide this feature or, as in my case, the database in question does support this feature but the DBA's don't want anything to do with it? In case of a parent domain object having a collection of many child objects, you still might want to have a one-shot delete capability instead of having separate DELETE statements for each child record.
The newly released NHibernate 2.1 (congratulations to the entire team for their efforts and hard work) comes to the rescue, which introduces a couple of new event listeners that deal with collections.
First we need an example. Suppose we are building an auction web site and the domain has a class called Item which in turn has a collection of Bids.
The mapping for these classes looks something like this:
Just to give you a general idea of the situation here. Now suppose we want to delete a quite popular Item object which has a numerous amount of Bids. Because the collection of Bids is mapped as inverse, NHibernate will remove every record for a Bid with a separate DELETE statement for each row.
We could solve this by creating a collection event listener. The first thing we have to do is figure out how to issue a one-shot delete instead of those separate DELETE statements.
We created an IOneShotDeleteHandler interface with one implementation for the Item class. The most notable aspect of this implementation is the use of the HQL delete statement that removes all Bids for a particular Item.
Next step is to create a collection event listener that implements the IPreCollectionRemoveEventListener interface.
Don't worry about the IEventListener interface. Its just there for registering all NHibernate event listeners in an IoC container.By doing so, it enables us to inject a collection of IOneShotDeleteHandler objects into the constructor of our event listener. When the OnPreRemoveCollection method is called, we simply lookup whether there's a handler available for the type of entity that's going to be deleted and give it a shot at removing its child collection in one sweep.
Now we only have to register this event listener:
Now, if we would use this 'as is', NHibernate will give us the following error:
Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
This is due to the fact that the collection event listener does its work and deletes all Bids by executing the HQL statement, but NHibernate still tries to issue a DELETE statement for each Bid. This is the result of the all-delete-orphan cascading rule we imposed in the mapping. We could reduce it to save-update, but then no individual DELETE statements are executed when a singe Bid is removed from the collection. Now what?
Well, we could provide a regular delete event listener that allows individual DELETE statements for Bid entities as long as their parent Item is not removed.
With both event listeners registered, deleting a single Bid results in a single DELETE statement as one would expect:
and removing an entire Item now results in a one-shot delete for all Bids:
public class Item
{
private ISet _bids;
public Int64 Id { get; private set; }
...
}
public class Bid
{
public Double Amount { get; private set; }
public Sting Code { get; private set; }
...
}
Make sure you use this solution for one-shot deletes wisely and only if you have to. If you can use the CASCADE DELETE foreign key constraints, then by all means, this is the preferred option. If not, only resort to this kind of solution only if you must and that you can prove that its going to give you a tremendous performance benefit. Also take a look at the batching support that NHibernate provides (at the moment only SQL Server and Oracle are supported).
Till next time