NServiceBus, DTC and ObjectDisposedException
tl;dr - don't call Session.Close() inside an NServiceBus handler.
We have been implementing NServiceBus for a project at work. It's been challenging in a lot of ways, as well as a big eye-opener. Anyway, we had a problem that took us quite a long time to work out, because the code that was causing it was written about a month ago an we just never actually used it until now. We were getting this in our log files:
2012-05-17 16:39:54,480 [Worker.7] DEBUG NServiceBus.Unicast.UnicastBus [(null)] - Calling 'HandleEndMessage' on NServiceBus.SagaPersisters.NHibernate.NHibernateMessageModule
2012-05-17 16:39:54,480 [11] DEBUG NHibernate.Impl.SessionImpl [(null)] - before transaction completion
2012-05-17 16:39:54,480 [11] DEBUG NHibernate.Transaction.ITransactionFactory [(null)] - [session-id=d4c6069a-0134-43b7-afca-e27781453a0a] Flushing from Dtc Transaction
2012-05-17 16:39:54,480 [11] DEBUG NHibernate.Transaction.ITransactionFactory [(null)] - prepared for DTC transaction
2012-05-17 16:39:54,496 [11] ERROR NHibernate.Transaction.ITransactionFactory [(null)] - DTC transaction prepre phase failed
System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'Transaction'.
at System.Transactions.Transaction.DependentClone(DependentCloneOption cloneOption)
at System.Transactions.TransactionScope.SetCurrent(Transaction newCurrent)
at System.Transactions.TransactionScope.PushScope()
at System.Transactions.TransactionScope.Initialize(Transaction transactionToUse, TimeSpan scopeTimeout, Boolean interopModeSpecified)
at System.Transactions.TransactionScope..ctor(Transaction transactionToUse)
at NHibernate.Transaction.AdoNetWithDistributedTransactionFactory.DistributedTransactionContext.System.Transactions.IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)
We tried heaps of things and did a lot of googling and found nothing that seemed relevant. The problem ended up being in this block of code:
private void WithinTransaction(Action<ISession> action)
{
using (var session = OpenSession()) {
using (var trans = session.BeginTransaction()) {
action(session);
trans.Commit();
}
session.Close(); // <-- Note, the session is being closed
}
}
After doing a lot of reading, it turns out that closing the NHibernate session is a bad idea. NServiceBus overrides the SessionFactory and I suppose it also overrides part of the ISession, but closing it breaks the DTC transaction manager. You should just let it dispose and the dispose handler will deal with closing the session when it needs to be closed.