NHibernate and the Unit of Work Pattern (Part 2)

In the first part of this series I have started to implement the Unit of Work pattern.

In this post I'll finish this implementation (for non-thread safe version) and in a third post I'll discuss how to make this implementation thread safe.

Design and Implementation

The Unit Of Work Factory

The creation of a unit of work instance is a complex process and as such is a good candidate for a factory.

Since a UoW (Unit of Work) is basically a wrapper around a NHibernate session object I'll need to open such a session whenever I start a new UoW. But to be able to get such a session NHibernate has to be configured and a NHibernate Session Factory has to be available. The interface of my UoW factory is defined as follows

image

I basically have two public methods on my factory. One for the creation of the UoW and the second for the disposal of a specific UoW instance. The disposal involves also some work and thus justifies the existence of this second factory method. The 3 properties Configuration, SessionFactory and CurrentSession are there mostly for advanced scenarios (and as such could theoretically be omitted in a first simple implementation).

Let's now write the test for the Create method of the UoW factory implementation. At first I prepare a new test fixture class

using System;
using NHibernate;
using NUnit.Framework;
 
namespace NHibernateUnitOfWork.Tests
{
    [TestFixture]
    public class UnitOfWorkFactory_Fixture
    {
        private IUnitOfWorkFactory _factory;
 
        [SetUp]
        public void SetupContext()
        {
            _factory = (IUnitOfWorkFactory) Activator.CreateInstance(typeof (UnitOfWorkFactory), true);
        }
    }
}

Why the heck do I need the Activator.CreateInstance to get an instance of the factory? Well I decided that the construction of a new factory instance should be internal to the assembly implementing the Unit of Work pattern. Thus the (default) constructor of the UnitOfWorkFactory class has a modifier internal and as a consequence cannot be used from code outside the assembly in which the factory is implemented. But I need an instance when testing and thus have to resort to the technique used above.

Now the question is: "what confirms me that the method Create of the factory works as expected?". I have chosen the following: I expect

  • the method returning a non null instance of type IUnitOfWork
  • the returned instance having a non null (NHibernate) session
  • the (NHibernate) session having the Flush Mode set to commit (that is: the session is never flushed except when I explicitly commit a transaction - see the NHibernate documentation for any details about the Flush Mode)

Now the code

[Test]
public void Can_create_unit_of_work()
{
    IUnitOfWork implementor = _factory.Create(); 
    Assert.IsNotNull(implementor);
    Assert.IsNotNull(_factory.CurrentSession);
    Assert.AreEqual(FlushMode.Commit, _factory.CurrentSession.FlushMode);
}

and the implementation

public class UnitOfWorkFactory : IUnitOfWorkFactory
{
    private static ISession _currentSession;
    private ISessionFactory _sessionFactory;
 
    internal UnitOfWorkFactory()
    { }
 
    public IUnitOfWork Create()
    {
        ISession session = CreateSession();
        session.FlushMode = FlushMode.Commit;
        _currentSession = session;
        return new UnitOfWorkImplementor(this, session);
    }
}

As you can see I first need a new session instance. I then set the flush mode to commit and assign the session to an instance variable for further use. Finally I return a new instance of the UnitOfWorkImplementor (which implements the interface IUnitOfWork). The constructor of the UnitOfWorkImplementor class requires two parameters, the session and a reference to this factory.

To be able to compile I also have to define the UnitOfWorkImplementor class. Of this class I just implement the minimum needed so far

public class UnitOfWorkImplementor : IUnitOfWork
{
    private readonly IUnitOfWorkFactory _factory;
    private readonly ISession _session;
 
    public UnitOfWorkImplementor(IUnitOfWorkFactory factory, ISession session)
    {
        _factory = factory;
        _session = session;
    }
}

Now back to the implementation of the CreateSession method. To be able to create (and open) a session NHibernate must have been configured previously. So I solve this problem first.

First we add a file hibernate.cfg.xml to our test project and put the following content into it (don't forget to set the property 'Copy to Output Directory' of this file to 'Copy always').

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
  <session-factory>
    <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
    <property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>
    <property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
    <property name="connection.connection_string">Server=(local);Database=Test;Integrated Security=SSPI;</property>
    
    <property name="show_sql">true</property>
  </session-factory>
</hibernate-configuration>

The above configuration file assumes that you have an SQL Server 2005 installed on your local machine and that you are using integrated security to authenticate and authorize the access to the database. It further assumes that there exists a database called 'Test' on this server. To configure NHibernate for other types of databases please consult the online documentation here.

Now we define this test method

[Test]
public void Can_configure_NHibernate()
{
    var configuration = _factory.Configuration;
    Assert.IsNotNull(configuration);
    Assert.AreEqual("NHibernate.Connection.DriverConnectionProvider", 
                    configuration.Properties["connection.provider"]);
    Assert.AreEqual("NHibernate.Dialect.MsSql2005Dialect", 
                    configuration.Properties["dialect"]);
    Assert.AreEqual("NHibernate.Driver.SqlClientDriver", 
                    configuration.Properties["connection.driver_class"]);
    Assert.AreEqual("Server=(local);Database=Test;Integrated Security=SSPI;", 
                    configuration.Properties["connection.connection_string"]);
}

Here we test whether we can successfully create a configuration and whether this configuration contains the attributes we have defined via the NHibernate configuration file.

To fulfill this test we can e.g. implement the following code

public Configuration Configuration
{
    get
    {
        if (_configuration == null)
        {
            _configuration = new Configuration();
            string hibernateConfig = Default_HibernateConfig;
            //if not rooted, assume path from base directory
            if (Path.IsPathRooted(hibernateConfig) == false)
                hibernateConfig = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, hibernateConfig);
            if (File.Exists(hibernateConfig))
                _configuration.Configure(new XmlTextReader(hibernateConfig));
        }
        return _configuration;
    }
}

Note that I have defined a class variable _configuration such as that I only execute the configuration logic once during the life time of the application. Note further that in this implementation I assume that the configuration of NHibernate is defined via the hibernate.cfg.xml file which must be in the same directory as the test assembly.

As soon as I have a valid configuration I can create a NHibernate session factory. Note that this is a rather expensive operation (takes some time depending on the number of entities you are mapping) and thus should only be executed once during the life time of the application. The access to the NHibernate SessionFactory is thread safe!

Let's write a test for the creation of the SessionFactory.

[Test]
public void Can_create_and_access_session_factory()
{
    var sessionFactory = _factory.SessionFactory;
    Assert.IsNotNull(sessionFactory);
    Assert.AreEqual("NHibernate.Dialect.MsSql2005Dialect", sessionFactory.Dialect.ToString());
}

This is a rather basic test and only checks whether I can successfully create a session factory and whether at least one of its properties is configured as I expect it, namely the dialect used.

The implementation to fulfill the test is rather simple

public ISessionFactory SessionFactory
{
    get
    {
        if (_sessionFactory == null)
            _sessionFactory = Configuration.BuildSessionFactory();
        return _sessionFactory;
    }
}

I have defined a class variable _sessionFactory such that I only execute the BuildSessionFactory method once during the life time of the application. The session factory is created on demand, that is when first needed. At the same time this property getter method accesses the previously defined Configuration property which in turn triggers the configuration of NHibernate (if not already done).

The last missing piece is the implementation of the CreateSession method which now is trivial

private ISession CreateSession()
{
    return SessionFactory.OpenSession();
}
I want to be able to access the current (open) session via my factory. If there is no such session open at the moment a meaningful exception should be raised. To test this write
[Test]
public void Accessing_CurrentSession_when_no_session_open_throws()
{
    try
    {
        var session = _factory.CurrentSession;
    }
    catch (InvalidOperationException)
    { }
}

and the implementation is easy

public ISession CurrentSession
{
    get
    {
        if (_currentSession == null)
            throw new InvalidOperationException("You are not in a unit of work.");
        return _currentSession;
    }
    set { _currentSession = value; }
}

The last method to implement is the UoW disposal method. In this method I want to reset the CurrentSession to null and then forward the call to our static UnitOfWork class (defined in the first part of this post series) such as that this class can also make an internal clean-up. The implementation might look as follows

public void DisposeUnitOfWork(UnitOfWorkImplementor adapter)
{
    CurrentSession = null;
    UnitOfWork.DisposeUnitOfWork(adapter);
}

That's all we have to do for now regarding the UoW factory. Again we have implemented it in a TDD way. When we later re-factor our implementation to make it thread-safe this TDD approach will be a huge benefit since we have a complete test coverage of out code.

The Unit of Work Implementor

This class defines an actual Unit of Work instance. It implements the already mentioned (and defined) interface IUnitOfWork which in turn inherits from IDisposal. This allows us to work with a UoW using the following syntax

using(UnitOfWork.Start())
{
    // use Unit of Work
}

at the end of the block the Dispose method of the UoW will be called. So let's first think of a test for the implementation of this method. Here we have to use mocking to be able to test our SUT (system under test) in isolation. So let me setup a test fixture first.

[TestFixture]
public class UnitOfWorkImplementor_Fixture
{
    private readonly MockRepository _mocks = new MockRepository();
    private IUnitOfWorkFactory _factory;
    private ISession _session;
    private IUnitOfWorkImplementor _uow;
 
    [SetUp]
    public void SetupContext()
    {
        _factory = _mocks.DynamicMock<IUnitOfWorkFactory>();
        _session = _mocks.DynamicMock<ISession>();
    }
}

Again I use Rhino.Mocks as my mocking framework. My UoW instance has two external dependencies namely the UnitOfWork factory and the NHibernate session. In the SetupContext I generate a dynamic mock for each of them.

Now I  can write my test method

[Test]
public void Can_Dispose_UnitOfWorkImplementor()
{
    using(_mocks.Record())
    {
        Expect.Call(() => _factory.DisposeUnitOfWork(null)).IgnoreArguments();
        Expect.Call(_session.Dispose);
    }
    using (_mocks.Playback())
    {
        _uow = new UnitOfWorkImplementor(_factory, _session);
        _uow.Dispose();
    }
}

During the recording phase I define my expectations (of what should happen when calling the Dispose method of the UoW). First the DisposeUnitOfWork method of the factory and second the Dispose method of the session object should be called.

Here is the code which fulfills this test

public void Dispose()
{
    _factory.DisposeUnitOfWork(this);
    _session.Dispose();
}

As you can see I just forward the call to the Unit of Work factory and dispose my internal (NHibernate) session instance as formulated in the expectations of the test.

When working with a UoW I also have to be able to use transactions. Thus I need a way to "play" with transactions. Let's call the corresponding methods BeginTransaction and TransactionalFlush.

Generic Transaction

To shield the client code from all NHibernate specifics and to provide a simple(r) interface I define a GenericTransaction class (which implements the interface IGenericTransaction) which is just a wrapper around the NHibernate transaction.

I think that the implementation is trivial and doesn't need any further explanation

public interface IGenericTransaction : IDisposable
{
    void Commit();
    void Rollback();
}
 
public class GenericTransaction : IGenericTransaction
{
    private readonly ITransaction _transaction;
 
    public GenericTransaction(ITransaction transaction)
    {
        _transaction = transaction;
    }
 
    public void Commit()
    {
        _transaction.Commit();
    }
 
    public void Rollback()
    {
        _transaction.Rollback();
    }
 
    public void Dispose()
    {
        _transaction.Dispose();
    }
}

just note that the IGenericTransaction inherits form IDisposable.

With this definition of a generic transaction we can proceed to the implementation of the BeginTransaction and TransactionalFlush methods.

First I'll implement the BeginTransaction method. As usual I define the test first

[Test]
public void Can_BeginTransaction()
{
    using(_mocks.Record())
    {
        Expect.Call(_session.BeginTransaction()).Return(null);
    }
    using (_mocks.Playback())
    {
        _uow = new UnitOfWorkImplementor(_factory, _session);
        var transaction = _uow.BeginTransaction();
        Assert.IsNotNull(transaction);
    }
}

I expect that during execution of the method under test the BeginTransaction method of the NHibernate session is called. After calling BeginTransaction on the UoW (in the Playback phase) I additionally test whether I get a not-null transaction.

I also want to be able to start a transaction and define an isolation level for the transaction. The corresponding test might look like

[Test]
public void Can_BeginTransaction_specifying_isolation_level()
{
    var isolationLevel = IsolationLevel.Serializable;
    using(_mocks.Record())
    {   
        Expect.Call(_session.BeginTransaction(isolationLevel)).Return(null);
    }
    using (_mocks.Playback())
    {
        _uow = new UnitOfWorkImplementor(_factory, _session);
        var transaction = _uow.BeginTransaction(isolationLevel);
        Assert.IsNotNull(transaction);
    }
}

To fulfill the test(s) I just have to create a new instance of a generic transaction and pass the result of the _session.BeginTransaction call to the constructor (that is: a NHibernate ITransaction object)

public IGenericTransaction BeginTransaction()
{
    return new GenericTransaction(_session.BeginTransaction());
}
 
public IGenericTransaction BeginTransaction(IsolationLevel isolationLevel)
{
    return new GenericTransaction(_session.BeginTransaction(isolationLevel));