In Patterns of Enterprise Application Architecture, the Unit of Work design pattern is defined as:
Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.
NHibernate seems to have a great implementation of the unit of work, but understanding when to start and commit the unit of work without repeating yourself can be a little tricky. One thing we’ve been doing is starting a unit of work using an interceptor.
[Interceptor(typeof (IUnitOfWorkInterceptor))]
public class AccountTasks : IAccountTasks
{
public bool are_valid(ICredentials credentials)
{
...
}
}
Account tasks is a service layer piece, that is decorated with an interceptor that will begin and commit a unit of work.
public interface IUnitOfWorkInterceptor : IInterceptor
{
}
public class UnitOfWorkInterceptor : IUnitOfWorkInterceptor
{
private readonly IUnitOfWorkFactory factory;
public UnitOfWorkInterceptor(IUnitOfWorkFactory factory)
{
this.factory = factory;
}
public void Intercept(IInvocation invocation)
{
using (var unit_of_work = factory.create()) {
invocation.Proceed();
unit_of_work.commit();
}
}
}
The interceptor starts a new unit of work, before proceeding with the invocation. If no exceptions are raised the unit of work is committed. If a unit of work is already started, the unit of work factory returns an empty unit of work. This ensures that if a service layer method calls into another method that it doesn’t start another unit of work.
public interface IUnitOfWorkFactory : IFactory<IUnitOfWork>
{
}
public class UnitOfWorkFactory : IUnitOfWorkFactory
{
private readonly IApplicationContext context;
private readonly IDatabaseSessionFactory factory;
private readonly TypedKey<ISession> key;
public UnitOfWorkFactory(IApplicationContext context, IDatabaseSessionFactory factory)
{
this.context = context;
this.factory = factory;
key = new TypedKey<ISession>();
}
public IUnitOfWork create()
{
if (unit_of_work_is_already_started()) {
return new EmptyUnitOfWork();
}
return create_a_unit_of_work().start();
}
private bool unit_of_work_is_already_started()
{
return context.contains(key);
}
private IUnitOfWork create_a_unit_of_work()
{
var session = factory.create();
context.add(key, session);
return new UnitOfWork(session, context);
}
}
The implementation of the repository pulls the active session from the application context.
public class DatabaseRepository<T> : IRepository<T>
{
private readonly IApplicationContext context;
private readonly IKey<ISession> session_key;
public DatabaseRepository(IApplicationContext context)
{
this.context = context;
session_key = new TypedKey<ISession>();
}
public IQueryable<T> all()
{
return the_current_session().Linq<T>();
}
public void save(T item)
{
the_current_session().SaveOrUpdate(item);
}
public void delete(T item)
{
the_current_session().Delete(item);
}
private ISession the_current_session()
{
var current_session = context.get_value_for(session_key);
if (null == current_session || !current_session.IsOpen) {
throw new NHibernateSessionNotOpenException();
}
return current_session;
}
}