NHibernate Repository and Unit Of Work

In my previous post we looked at the domain model and the class maps associated with it. In this post lets look at the Repository and Unit Of Work implementations.

Note: The following is based on the FubuMVC contrib project.

IRepository

A repository is an abstraction layer which gives your application an in memory domain object collection. This layer helps in making you application agnostic about where the data is coming from or where it is persisted into. It could well be a flat file or any other RDBMS. Preferably each of your domain object would have its own repository. But for the sake of keeping it simple, I am using a generic repository in the following code.

namespace MPBlog.Core.BaseClasses
{
    using System;
    using System.Linq;
    using System.Linq.Expressions;

    public interface IRepository
    {
        T Load<T>(int id) where T : Entity<T>;
        void Delete<T>(T target);
        void Save<T>(T target);

        IQueryable<T> Query<T>() where T : Entity<T>;

        IQueryable<T> Query<T>(
            Expression<Func<T, bool>> whereQuery) where T : Entity<T>;
    }
}

The Query methods here will be implemented using Linq to NHibernate. Now that we have the interface, lets look at the implementation.

NHRepository

namespace MPBlog.Persistence.RepositoryImpl
{
    using System;
    using System.Linq;
    using System.Linq.Expressions;
    using Core.BaseClasses;
    using NHibernate.Linq;
    using UnitOfWork;

    public class NHRepository : IRepository
    {
        private INHibernateUnitOfWork _unitOfWork;

        public NHRepository(
            INHibernateUnitOfWork unitOfWork)
        {
            _unitOfWork = unitOfWork;
        }

        public T Load<T>(
            int id) where T : Entity<T>
        {
            return _unitOfWork.Session.Load<T>(id);
        }

        public void Delete<T>(
            T target)
        {
            _unitOfWork.Session.Delete(target);
        }

        public void Save<T>(
            T target)
        {
            _unitOfWork.Session.Save(target);
        }

        public IQueryable<T> Query<T>() where T : Entity<T>
        {
            return _unitOfWork.Session.Linq<T>();
        }

        public IQueryable<T> Query<T>(
            Expression<Func<T, bool>> whereQuery) where T : Entity<T>
        {
            return _unitOfWork.Session.Linq<T>().Where(whereQuery);
        }
    }
}

If you notice the above code, we are accessing the sessionthrough the unitofwork. A unit of work helps in keeping track of all the objects that are affect by a business transaction. Following is the implementation of the UnitOfWork.

IUnitOfWork and INHibernateUnitOfWork

namespace MPBlog.Core.BaseClasses
{
    using System;

    public interface IUnitOfWork : IDisposable
    {
        void Initialize();
        void Commit();
        void Rollback();
    }
}
namespace MPBlog.Persistence.UnitOfWork
{
    using Core.BaseClasses;
    using NHibernate;

    public interface INHibernateUnitOfWork : IUnitOfWork
    {
        ISession Session { get; }
    }
}

NHibernateUnitOfWork

namespace MPBlog.Persistence.UnitOfWork
{
    using System;
    using FluentNHibernate;
    using NHibernate;

    public class NHibernateUnitOfWork : INHibernateUnitOfWork
    {
        private ITransaction _transaction;
        private readonly ISessionSource _source;
        private bool _isDisposed;
        private bool _isInitialized;
        public ISession Session { get; private set; }

        public NHibernateUnitOfWork(
            ISessionSource source)
        {
            _source = source;
        }

        public void Initialize()
        {
            CheckUoWSanity();
            Session = _source.CreateSession();
            StartNewTransaction();
            _isInitialized = true;
        }

        public void Commit()
        {
            CheckUoWSanity();
            CheckUoWInitialization();
            _transaction.Commit();
            StartNewTransaction();
        }

        public void Rollback()
        {
            CheckUoWSanity();
            CheckUoWInitialization();
            _transaction.Rollback();
            StartNewTransaction();
        }

        public void Dispose()
        {
            if (_isDisposed || !_isInitialized) return;
            _transaction.Dispose();
            Session.Dispose();
            _isDisposed = true;
        }

        private void CheckUoWSanity()
        {
            if (_isDisposed)
            {
                throw new ObjectDisposedException("Trying to use disposed object");
            }
        }

        private void CheckUoWInitialization()
        {
            if (!_isInitialized)
            {
                throw new InvalidOperationException("NHibernateUnitOfWork is not initialized");
            }
        }

        private void StartNewTransaction()
        {
            if (_transaction != null)
            {
                _transaction.Dispose();
            }

            _transaction = Session.BeginTransaction();
        }
    }
}

Tests

Following are the test that were written for both the repository and unitofwork. I am using MoQ as my mocking framework so that I don’t have to connect to the actual database when testing.

namespace MPBlog.Tests.Persistence.Tests
{
    using System.Linq;
    using Core.Domain;
    using Moq;
    using MPBlog.Persistence.RepositoryImpl;
    using MPBlog.Persistence.UnitOfWork;
    using NHibernate;
    using Xunit;

    public class RepositoryTests
    {
        private Mock<ISession> _session;
        private Mock<INHibernateUnitOfWork> _uow;
        private Mock<NHRepository> _repo;

        public RepositoryTests()
        {
            _session = new Mock<ISession>();
            _uow = new Mock<INHibernateUnitOfWork>();
            _uow.Setup(u => u.Session).Returns(_session.Object);
            _repo = new Mock<NHRepository>(_uow.Object);
        }

        [Fact]
        public void Call_to_load_should_load_object_from_the_session()
        {
            var postId = 1;
            _repo.Object.Load<Post>(postId);
            _session.Verify(
                s => s.Load<Post>(postId),
                Times.AtLeastOnce());
        }

        [Fact]
        public void Call_to_save_should_save_object_on_the_session()
        {
            var post = new Post();
            _repo.Object.Save(post);
            _session.Verify(
                s => s.SaveOrUpdate(post),
                Times.AtLeastOnce());
        }

        [Fact]
        public void Call_to_delete_should_delete_object_from_the_session()
        {
            var post = new Post();
            _repo.Object.Delete(post);
            _session.Verify(
                s => s.Delete(post),
                Times.AtLeastOnce());
        }

        [Fact]
        public void Call_to_query_on_session_should_return_an_IQueryable_object()
        {
            Assert.IsAssignableFrom(
                typeof(IQueryable<Post>),
                _repo.Object.Query<Post>());
        }
    }
}
namespace MPBlog.Tests.Persistence.Tests
{
    using FluentNHibernate;
    using Moq;
    using MPBlog.Persistence.UnitOfWork;
    using NHibernate;
    using Xunit;

    public class UnitOfWorkTests
    {
        protected Mock<ISession> _session;
        protected Mock<ITransaction> _transaction;
        protected Mock<ISessionSource> _sessionSource;
        protected Mock<NHibernateUnitOfWork> _uow;

        public UnitOfWorkTests()
        {
            _session = new Mock<ISession>();
            _transaction = new Mock<ITransaction>();
            _sessionSource = new Mock<ISessionSource>();
            _sessionSource.Setup(s => s.CreateSession()).Returns(_session.Object);
            _session.Setup(s => s.BeginTransaction()).Returns(_transaction.Object);
            _uow = new Mock<NHibernateUnitOfWork>(_sessionSource.Object);
            _uow.Object.Initialize();
        }

        [Fact]
        public void Can_create_new_transaction()
        {
            _session.Verify(
                s => s.BeginTransaction(),
                Times.AtLeastOnce());
        }

        [Fact]
        public void Can_dispose_the_transaction()
        {
            _uow.Object.Dispose();
            _transaction.Verify(
                s => s.Dispose(),
                Times.Exactly(1));
        }

        [Fact]
        public void Can_dispose_the_session()
        {
            _uow.Object.Dispose();
            _session.Verify(
                s => s.Dispose(),
                Times.Exactly(1));
        }

        [Fact]
        public void Call_to_commit_should_commit_the_transaction()
        {
            _uow.Object.Commit();
            _transaction.Verify(
                t => t.Commit(),
                Times.AtLeastOnce());
        }

        [Fact]
        public void Call_to_commit_should_dispose_the_transaction_and_start_a_new_transaction()
        {
            _uow.Object.Commit();
            _transaction.Verify(
                t => t.Dispose(),
                Times.Exactly(1));
            _session.Verify(
                s => s.BeginTransaction(),
                Times.Exactly(2));
        }

        [Fact]
        public void Call_to_rollback_should_rollback_the_transaction()
        {
            _uow.Object.Rollback();
            _transaction.Verify(
                t => t.Rollback(),
                Times.AtLeastOnce());
        }

        [Fact]
        public void Call_to_rollback_should_dispose_the_transaction_and_start_a_new_transaction()
        {
            _uow.Object.Rollback();
            _transaction.Verify(
                t => t.Dispose(),
                Times.Exactly(1));
            _session.Verify(
                s => s.BeginTransaction(),
                Times.Exactly(2));
        }
    }
}