Apr 29 2010

NHibernate soft-deletes: moving an entity to another table

Category: zvolkov @ 16:11

Here's my scenario: I have two tables, Transactions and DeletedTransactions with identical schema. I need to delete a Transaction from the first table, and save it to the second. (I know I could do it with triggers, but what fun would that be?)

The simplest solution would be to create two classes, map each one to its corresponding table, then simply copy all fields from one object to another. A more elegant solution would be to use the entity-name approach, to override the mechanism by which NH decides which mapping to use for this particular operation. Basically, the idea is to map Transaction class to both tables by creating two mappings with different values of the optional entity-name attribute (which normally defaults to full class name). Unfortunately, as explained in the article, this approach has one serious drawback: every single place where you retrieve or persist the entities has to be modified to explicitly specify the entity name. This means all Save, Get, and CreateCriteria operations for both Transactions and DeletedTransactions have to be modified!

Fortunately by a clever use of polymorphism we can trick NH into using Transactions mapping by default, and only using DeletedTransactions when explicitly specified. This way the only places you have to change are those where DeletedTransactions are created. Finally, we can encapsulate creation of DeletedTransactions into a listener class using some nice NH Event Listener magic. Here are the step-by-step instructions:

1. Define your DeletedTransaction class as normally:

public class DeletedTransaction
{
	public virtual long Id { get; set; }
	public virtual DateTime TradeDate { get; set; }
	//other properties
}

2. Derive your Transaction from DeletedTransaction, this will allow casting any Transaction to DeletedTransaction:

public class Transaction : DeletedTransaction
{ 
	//no properties defined here as the two classes are identical
}

3. Create normal Transactions mapping (don't override entity-name):

<class name="Transaction" table="Transactions">
    <id name="Id" column="TransactionID">
      <generator class="identity"/>
    </id>
	<!-- properties etc. -->
</class>

4. Create normal DeletedTransactions mapping (don't override entity-name either). Remember to use generator class=assigned for the identity column if you want to keep the identity value:

<class name="DeletedTransaction" table="DeletedTransactions">
    <id name="Id" column="TransactionID">
      <generator class="assigned" />
    </id>
	<!-- properties etc. -->
</class>

5. Create PreDelete event listener that will create DeletedTransaction whenever Transaction is deleted:

public class HandleDeletedTransactions : IPreDeleteEventListener
    {
        public bool OnPreDelete(PreDeleteEvent deleted)
        {
            if (deleted.Entity is Transaction)
            {
                var tran = deleted.Entity as Transaction;
                var session = deleted.Session.GetSession(EntityMode.Poco);
                tran.DeletedDate = DateTime.Now;
                tran.DeletedByUserID = 1973;

                //insert into DeletedTransactions
                //(NH uses object's type name to figure out which mapping to use
                //this will tell NH to treat this Transaction as DeletedTransaction instead):
                session.Save(typeof(DeletedTransaction).FullName, tran);

                session.Flush();
            }
            return false;
        }
    }

6. Register the listener with NH configuration (here I am showing how to do it with the code, you can do it with XML config as well)

configuration.SetListener(ListenerType.PreDelete, new HandleDeletedTransactions());

No other code needs to be modified. Indeed, all the Get and CreateCriteria explicitely specify the class, and the Save and Update of Transactions get the class from GetType() which will obviously return "Transaction" and not its base "DeletedTransaction"!

Whenever it feels just a little bit dirty but does the trick I say: "Nice hack!"

Tags:

Comments

1.
trackback DotNetKicks.com says:

NHibernate soft-deletes: moving an entity to another table

You've been kicked (a good thing) - Trackback from DotNetKicks.com

2.
Mikael Henriksson Mikael Henriksson says:

It's damn easy way of doing it, I never considered inheritance to solve that problem so I have been copying forth and back all this time. This will save me many lines of code in the future.

3.
Toronto Toronto says:

I agree, it takes a little more coding, but it does save a lot of time in the long run. Not sure how you came across this method Andrei, but thanks for sharing

Comments are closed