NHibernate, transactions and TransactionScope
- by Erik
I'm trying to find the best solution to handle transaction in a web application that uses NHibernate.
We use a IHttpModule and at HttpApplication.BeginRequest we open a new session and we bind it to the HttpContext with ManagedWebSessionContext.Bind(context, session); We close and unbind the session on HttpApplication.EndRequest.
In our Repository base class, we always wrapped a transaction around our SaveOrUpdate, Delete, Get methods like, according to best practice:
public virtual void Save(T entity)
{
var session = DependencyManager.Resolve<ISession>();
using (var transaction = session.BeginTransaction())
{
session.SaveOrUpdate(entity);
transaction.Commit();
}
}
But then this doesn't work, if you need to put a transaction somewhere in e.g. a Application service to include several repository calls to Save, Delete, etc..
So what we tried is to use TransactionScope (I didn't want to write my own transactionmanager). To test that this worked, I use an outer TransactionScope that doesn't call .Complete() to force a rollback:
Repository Save():
public virtual void Save(T entity)
{
using (TransactionScope scope = new TransactionScope())
{
var session = Depe.ndencyManager.Resolve<ISession>();
session.SaveOrUpdate(entity);
scope.Complete();
}
}
The block that uses the repository:
TestEntity testEntity = new TestEntity { Text = "Test1" };
ITestRepository testRepository = DependencyManager.Resolve<ITestRepository>();
testRepository.Save(testEntity);
using (var scope = new TransactionScope())
{
TestEntity entityToChange = testRepository.GetById(testEntity.Id);
entityToChange.Text = "TestChanged";
testRepository.Save(entityToChange);
}
TestEntity entityChanged = testRepository.GetById(testEntity.Id);
Assert.That(entityChanged.Text, Is.EqualTo("Test1"));
This doesn't work. But to me if NHibernate supports TransactionScope it would! What happens is that there is no ROLLBACK at all in the database but when the testRepository.GetById(testEntity.Id); statement is executed a UPDATE with SET Text = "TestCahgned" is fired instead (It should have been fired between BEGIN TRAN and ROLLBACK TRAN). NHibernate reads the value from the level1 cache and fires a UPDATE to the database. Not expected behaviour!? From what I understand whenever a rollback is done in the scope of NHibernate you also need to close and unbind the current session.
My question is: Does anyone know of a good way to do this using TransactionScope and ManagedWebSessionContext?