Wednesday, 4 May 2011

Unit of Work

Health warning – The code featured in this post hasn’t been tested. These are just my musings on the subject of the Unit of Work pattern.

The Unit of Work pattern has been around for quite a while. I first encountered this pattern when I adopted NHibernate as the basis of my data access code. In NHibernate the Session object represents a Unit of Work. I’ve been investigating the Microsoft Entity Framework again - the latest code first option in particular - and I’ve noticed that in many examples the Unit of Work pattern is being implemented explicitly. This is particularly prevalent when the Repository pattern is being used.

So, what is the Unit of Work pattern?

Martin Fowler defines Unit of Work in the following terms:

"Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.

…When you're pulling data in and out of a database, it's important to keep track of what you've changed; otherwise, that data won't be written back into the database. Similarly you have to insert new objects you create and remove any objects you delete…

…A Unit of Work keeps track of everything you do during a business transaction that can affect the database. When you're done, it figures out everything that needs to be done to alter the database as a result of your work." *

Folwer goes on to suggest an interface for the Unit of Work pattern. In C# – and taking advantage of Generics - the interface might look something like this:

public interface IUnitOfWork<T>
{
    void RegisterNew(T instance);
    void RegisterDirty(T instance);
    void RegisterClean(T instance);
    void RegisterDeleted(T instance);
    void Commit();
    void Rollback();
}

For my purposes I think I can drop the RegisterClean(T instance) and Rollback() methods leaving:

public interface IUnitOfWork<T>
{
    void RegisterNew(T instance); // for tracking new instances
    void RegisterDirty(T instance); // for tracking updated instances 
    void RegisterDeleted(T instance); // for tracking deleted instances
    void Commit(); // to flush registered changes to the backing store
}

Many people seem to remove the Rollback() method and simply choose to not commit changes when something goes wrong. If you need to work with transactions you might choose differently.

Once you have an interface for your Unit of Work you need to decide what is going to implement it. I have observed 2 basic approaches to this:

  1. Create a Unit of Work class that implements the IUnitOfWork<T> interface and pass this to a Repository. In this case the IUnitOfWork<T> acts as a wrapper or adapter for the NHibernate session or EF context etc.
  2. Have your repository implement IUnitOfWork<T>. In this case the Repository is the Unit of Work.

I do not like the second option because I think it blurs the distinction between the 2 patterns. When we look at things from the point of view of separation of concerns or single responsibility the first option looks better to me.

So, given our IUnitOfWork<T> interface, lets imagine an NHibernate implementation of the Unit of Work as well as a Repository:

public class NHibernateUnitOfWork<T> : IUnitOfWork<T>
{
    private ISession _session;

    public NHibernateUnitOfWork(ISession session)
    {
        _session = session;
    }

    public void RegisterNew(T instance)
    {
        _session.Save(instance);
    }

    public void RegisterDirty(T instance)
    {
        _session.Update(instance);
    }

    public void RegisterDeleted(T instance)
    {
        _session.Delete(instance);
    }

    public void Commit()
    {
        _session.Flush();
    }
}

public interface IRepository<T>
{
    void Add(T instance);
    void Remove(T instance);
    void Update(T instance);
}

public class Repository<T> : IRepository<T>
{
    private IUnitOfWork<T> _unitOfWork;

    public Repository(IUnitOfWork<T> unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    public void Add(T instance)
    {
        _unitOfWork.RegisterNew(instance);
    }

    public void Remove(T instance)
    {
        _unitOfWork.RegisterDeleted(instance);
    }

    public void Update(T instance)
    {
        _unitOfWork.RegisterDirty(instance);
    }
}

public class User
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

To make use of the Unit of Work we might do something like this:

//Configure NHibernate
var configuration = new Configuration();
configuration.Configure();

ISessionFactory sessionFactory = configuration.BuildSessionFactory();
ISession session = sessionFactory.OpenSession();

// Create and use a Unit of Work
IUnitOfWork<User> unitOfWork = new NHibernateUnitOfWork<User>(session);
IRepository<User> repository = new Repository<User>(unitOfWork);

var user = new User {FirstName = "Andy", LastName = "French"};

repository.Add(user);
// Maybe do other things with the repository here...
            
unitOfWork.Commit();

Note that in this example we are simply delegating to the NHibernate session which already represents a Unit of Work. Rather than do this some people like to keep lists of the affected entities in the Unit of Work class and then process the lists as a batch in the Commit() method. Perhaps such an implementation would look like this:

public class NHibernateUnitOfWork<T> : IUnitOfWork<T>
{
    private ISession _session;
    private IList<T> _added = new List<T>();
    private IList<T> _deleted = new List<T>();
    private IList<T> _updated = new List<T>();

    public NHibernateUnitOfWork(ISession session)
    {
        _session = session;
    }

    public void RegisterNew(T instance)
    {
        _added.Add(instance);
    }

    public void RegisterDirty(T instance)
    {
        _updated.Add(instance);
    }

    public void RegisterDeleted(T instance)
    {
        _deleted.Add(instance);
    }

    public void Commit()
    {
        using (ITransaction transaction = _session.BeginTransaction())
        {
            ProcessAdded();
            ProcessUpdated();
            ProcessDeleted();

            transaction.Commit();
        }
    }

    private void ProcessAdded()
    {
        foreach(var instance in _added)
        {
            _session.Save(instance);
        }
    }

    private void ProcessUpdated()
    {
        foreach (var instance in _updated)
        {
            _session.Update(instance);
        }
    }

    private void ProcessDeleted()
    {
        foreach (var instance in _deleted)
        {
            _session.Delete(instance);
        }
    }
}

Note that in this version we’ve been able to use NHibernate transaction support. Choose your poison.

References

* Unit of Work, Martin Fowler, http://martinfowler.com/eaaCatalog/unitOfWork.html

0 comments:

Post a Comment

By all means leave a comment. I may not be able to get back to you as quickly as I'd like but I'll do my very best.