Fluent NHibernate Unit Of Work Pattern
I've been trying to understand how to fit NHibernate into a MVC web application. I want a clean separation of model and data layer, so I can unit test the model. I'm planning on using the repository pattern for saving and retrieving objects. The other pattern I need to make a clean separation from the data layer is the Unit Of Work pattern.
Fowler defines the Unit Of Work pattern as:
Unit of Work Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.
So Fowler's unit of work has three responsibilities. In my application the repositories are going to maintain lists of objects affected by the transaction. Concurrency problems, I'm not sure about, but I think will be handled in the application logic. That leaves the Unit Of Work with the single responsibility for handling transactions, so my Unit Of Work is:
Unit of Work (ITransaction) An interface to the database transaction, which is careful not to expose details of the persistence mechanism.
It's arguable that what I'm talking is really an ITransaction. However NHibernate has already used that name, so I'm gonna stick with IUnitOfWork to avoid the name clash.
Looking round for an implementation to copy, I eventually found Weston Binford's Bootstrapping NHibernate with StructureMap, which was just what I wanted. The following code bears a remarkable similarity to his implementation.
public interface IUnitOfWork : IDisposable
{
void Rollback();
void Commit();
}
public class UnitOfWork : IUnitOfWork
{
private readonly ISessionFactory sessionFactory;
private readonly ITransaction transaction;
public UnitOfWork(ISessionFactory sessionFactory)
{
this.sessionFactory = sessionFactory;
Session = this.sessionFactory.OpenSession();
transaction = Session.BeginTransaction();
}
public ISession Session { get; private set; }
public void Dispose()
{
Session.Close();
Session = null;
}
public void Rollback()
{
if(transaction.IsActive)
transaction.Rollback();
}
public void Commit()
{
if(transaction.IsActive)
transaction.Commit();
}
}
I have some integration tests to convince myself that I understand what's going on. These tests use the same model objects and database as the first steps with fluent nhibernate article:
[TestFixture]
public class UnitOfWorkTest
{
private ISessionFactory SessionFactory { get; set; }
[TestFixtureSetUp]
public void CreateSessionFactory()
{
SessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(
"Server=(local);Database=NhibernateDemo;Trusted_Connection=True;"))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<StickyNote>())
.BuildSessionFactory();
}
[Test]
public void Uow_Commit_ShouldWork()
{
var note = new StickyNote() { Note = "Voo"};
using (var uow = new UnitOfWork(SessionFactory))
{
uow.Session.SaveOrUpdate(note);
uow.Commit();
}
using (var uow = new UnitOfWork(SessionFactory))
{
Assert.IsNotNull(uow.Session.Get<StickyNote>(note.Id));
}
}
[Test]
public void Uow_Rollback_ShouldWork()
{
var note = new StickyNote {Note = "Woo"};
using (var uow = new UnitOfWork(SessionFactory))
{
uow.Session.SaveOrUpdate(note);
uow.Rollback();
}
using (var uow = new UnitOfWork(SessionFactory))
{
Assert.IsTrue(note.Id > 0);
Assert.IsNull(uow.Session.Get<StickyNote>(note.Id));
}
}
[Test]
public void Uow_RollbackThenCommit_ShouldRollback()
{
var note = new StickyNote { Note = "Xoo" };
using (var uow = new UnitOfWork(SessionFactory))
{
uow.Session.SaveOrUpdate(note);
uow.Rollback();
uow.Commit();
}
using (var uow = new UnitOfWork(SessionFactory))
{
Assert.IsNull(uow.Session.Get<StickyNote>(note.Id));
}
}
[Test]
public void Uow_CommitThenRollback_ShouldCommit()
{
var note = new StickyNote { Note = "Yoo" };
using (var uow = new UnitOfWork(SessionFactory))
{
uow.Session.SaveOrUpdate(note);
uow.Commit();
uow.Rollback();
}
using (var uow = new UnitOfWork(SessionFactory))
{
Assert.IsNotNull(uow.Session.Get<StickyNote>(note.Id));
}
}
[Test]
public void Uow_WithNoCommit_ShouldRollback()
{
var note = new StickyNote { Note = "Zoo" };
using (var uow = new UnitOfWork(SessionFactory))
{
uow.Session.SaveOrUpdate(note);
}
using (var uow = new UnitOfWork(SessionFactory))
{
Assert.IsTrue(note.Id >0);
Assert.IsNull(uow.Session.Get<StickyNote>(note.Id));
}
}
}