Tuesday, March 8, 2011

NHibernate unit of work in MVC using Ninject (part 1)

We just released our third and (likely) final release of the application we've been working on for the last 11 months. During that time I've gone through two implementations of the NHibernate Unit of Work pattern. I will talk about it in two parts. The first part will talk about our first implementation, that turned out to be a little bit flawed and very incompatible with Ninject 2.1. The second one will talk about a better approach that we shipped with this final release.

I've previous blogged about NHibernate and the unit of work pattern here. The approach, I first decided to take was to create a generic Repository class. This class was not only responsible for CRUD operations and transaction management, but it also had 2 special methods on it called BeginRequest and EndRequest. Here's a simple implementation:

public class Repository : IRepository
{
    private ISession _session;
    public Repository(ISession session) { _session = session; }

    public Save<T>(T entity) where T : Entity
    {
        _session.SaveOrUpdate(entity);
    }
    public Get<T>(int id) where T : Entity
    {
        return _session.Get<T>(id);
    }
    public Delete<T>(T entity) where T : Entity
    {
        _session.Delete(entity);
    }

    public BeginTransaction() { _session.BeginTransaction(); }
    public CommitTransaction() { _session.Transaction.Commit(); }
    public RollbackTransaction() { _session.Transaction.Rollback(); }

    public BeginRequest() { BeginTransaction(); }
    public EndRequest() { CommitTransaction(); }
}

Obviously the real class was more robust and had much more error checking around transaction management, but those details aren't important for this discussion.

In addition to this Repository we also had an HttpModule that called Repository.BeginRequest() on BeginRequest and Repository.EndRequest() on EndRequest. It looked something like this:

public class DataModule : IHttpModule
{
 public void Init(HttpApplication context)
 {
  context.BeginRequest += BeginRequest;
  context.EndRequest += EndRequest;
 }

 public void Dispose()
 {
 }

 public void BeginRequest(object sender, EventArgs e)
 {
  var app = (WebApplication)sender;
  var repository = app.Kernel.Get<IRepository>().BeginRequest();
 }

 public void EndRequest(object sender, EventArgs e)
 {
  var app = (WebApplication)sender;
  var repository = app.Kernel.Get<IRepository>().EndRequest();
 }
}

Our Ninject bindings for these classes were as follows:

Bind<ISession>()
       .ToMethod(context => NHibernateSessionFactory.Instance.OpenSession())
       .InRequestScope();
Bind<IRepository>().To<Repository>().InTransientScope();

An important note is that the NHibernate session was bound in request scope, while the repository was bound in transient scope. So all instances of Repository in request scope, shared the same session. (NHibernateSessionFactory is our singleton in charge of configuring the SessionFactory and creating new sessions.)

This approach worked fine with Ninject 2.0, but suffered from several problems. First of all, we would be creating a new session and starting/commiting a new transaction on every http request. This means any request, even for content files like javascript or css, would trigger the DataModule. The second problem was, more architectural/stylistic. We had code that dealt with unit of work implementation inside a repository. We could have moved the Begin/End Request logic off to the DataModule and that would have alleviated this problem some what. However, we still would have been stuck with the first problem.

The kick in the pants for the rewrite came, when we upgraded to Ninject 2.1 and Ninject.Web.Mvc 2.1. Ninject 2.1 introduced a very eager way to ensure objects bound in Request scope didn't hang around after the EndRequest event. I'll talk about this and our implementation rewrite in part 2.

2 comments:

  1. Great post Vadim. Very approachable and some pretty good lessons learned.

    ReplyDelete
    Replies
    1. Glad you enjoyed it, be sure to check out Part 2 that contains a much better solution.
      http://vadimdev.blogspot.com/2011/03/nhibernate-unit-of-work-in-mvc-using_21.html

      Delete