Managing NHibernate Sessions with IoC

March 22, 2010

One aspect of our code that has bugged us here where I work is how little we really utilize the power of our IoC container.  It seemed that all we did was keep adding items to our config file, and that was it.  One idea we wanted to do was to utilize more of our container’s functionality, which includes managing an item’s lifestyle.

A good candidate for this was managing our NHibernate sessions, which were utilized in many places but always manually created and destroyed.  Ideally, if you’re using IoC, any class that needs a session should have it in their constructor, and that should be it.  So we came up with a solution that I will describe below that solves this.

Adding the Sessions to the IoC Container

The core of our NHibernate set up is that we use an HttpModule to handle the starting and ending of sessions.  Here is the code fired during the BeginRequest event:

if (!_sessionPerRequestModuleRegistered)
{
    _sessionPerRequestModuleRegistered = true;
    var sessionManager = IoC.Resolve<ISessionManager>();
    sessionManager.InitializeSessions();
}

Basically, we have a class-level flag that determines whether or not we have already loaded the NHibernate sessions for this request.  If not, we call a method on our ISessionManager class.  Here’s the relevant code for that:

public class SessionManager : ISessionManager
{
  private readonly SessionFactoryConfig _sessionFactoryConfig;
  private readonly ISessionFactoryManager _sessionFactoryManager;

  public SessionManager(SessionFactoryConfig sessionFactoryConfig, ISessionFactoryManager sessionFactoryManager)
  {
      _sessionFactoryConfig = sessionFactoryConfig;
      _sessionFactoryManager = sessionFactoryManager;
  }

  public void InitializeSessions()
  {
      foreach (var factoryConfig in _sessionFactoryConfig)
      {
          AddSessionToIoCContainer(factoryConfig.ToString());
      }
  }        

  private void AddSessionToIoCContainer(string configPath)
  {
      IoC.AddInstance(configPath, delegate
                                      {
                                          var session = _sessionFactoryManager.GetSessionFactory().OpenSession();
                                          session.BeginTransaction();
                                          return session;
                                      });            
  }    
}

Our SessionFactoryConfig class is simply a collection of possible NHibernate configs being used by the system (typically we only have one, but sometimes we will use more than one, so this makes the code more flexible – note that this example at the present time assumes only one config, no warranties made on its complete usefulness for multiple configs until I get to try it :) ).

So for each config, we call NHibernate to retrieve a session object, and also make sure to start the transaction before adding that session to our IoC container.  At a high-level, the key for our session in the IoC container will be the name of the NHibernate config file attached to that session.

Now at the end of our request life cycle, we need to commit all transactions and close all open sessions.  Here’s the code in our EndRequest event:

var sessionManager = IoC.Resolve<ISessionManager>();
try 
{
    // Commit every open session factory
    sessionManager.CommitAllTransactions();
}
finally
{
    // No matter what happens, make sure all the sessions get closed
    sessionManager.CloseAllSessions();
}

And here are the relevant methods in our SessionManager class:

public void CommitAllTransactions()
{
    foreach (var factoryConfig in _sessionFactoryConfig)
    {
        CommitTransaction(factoryConfig.ToString());
    }
}

public void CloseAllSessions()
{
    foreach (var factoryConfig in _sessionFactoryConfig)
    {
        CloseSession(factoryConfig.ToString());
    }
}

private void CommitTransaction(string configPath)
{
    var session = IoC.Resolve(configPath) as ISession;
    var transaction = session.Transaction;
    try
    {
        if (HasOpenTransaction(transaction))
        {
            transaction.Commit();
        }
    }
    catch (NHibernate.AdoNet.TooManyRowsAffectedException tmex)
    {
        // Squelch this for now, as it fires on batch deletes, but we'll log it
        Log<SessionManager>.Error("TooManyRowsAffectedException was thrown", tmex);
    }
    catch (HibernateException hex)
    {
        Log<SessionManager>.Error("HibernateException thrown", hex);
        RollbackTransaction(transaction, configPath);
        throw;
    }
}

private void RollbackTransaction(ITransaction transaction, string configPath)
{
    try
    {
        if (HasOpenTransaction(transaction))
        {
            transaction.Rollback();
        }
    }
    finally
    {
        CloseSession(configPath);
    }
}

private void CloseSession(string configPath)
{
    var session = IoC.Resolve(configPath) as ISession;
    session.Close();
}

/// <summary>
/// Checks for an open transaction in the specified Session.
/// </summary>
/// <param name="transaction">The transaction to check.</param>
/// <returns>
/// 	<see langword="true"/> if the <paramref name="transaction" /> is not null and open; otherwise, <see langword="false"/>.
/// </returns>
private bool HasOpenTransaction(ITransaction transaction)
{
    return transaction != null && !transaction.WasCommitted && !transaction.WasRolledBack;
}

As you can see, this retrieves all sessions from our IoC container and commits and closes them as expected.

The PerWebRequest Lifestyle

Note that our IoC container is Windsor, and it has the concept of Lifestyles, which indicate how long an item should remain in the container before it is disposed.  In order to manage sessions appropriate in our scenario above (where we open and close them at the beginning and end of a request, respectively), we need to ensure that we add them with the PerWebRequest lifestyle.  This tells Windsor to dispose the sessions once the request is over.  This ensures we do not use the same session on more than one request.

In the code above, when we add the sessions to the IoC container, you saw the IoC.AddInstance method being called.  This is what ensures the sessions go into the container with a PerWebRequest lifestyle.  Here is the code of that method:

public static void AddInstance<ServiceType>(string key, Function<ServiceType> factory)
{
    _defaultContainer.Register(Component.For<ServiceType>().UsingFactoryMethod(factory).Named(key).LifeStyle.PerWebRequest);
}

An important note: anything that depends on the session in the IoC container must also have the same PerWebRequest lifestyle in order for everything to be disposed of properly.  This includes the repositories that I describe below.

Using the Sessions

In our solution, we use repositories as our data access layer, and these inherit from a base Repository<T> class.  Now that our sessions have been added to IoC, we’ll need to inject them into the repositories appropriately.  As mentioned above, they need to be added with the PerWebRequest lifestyle, and the simples way to do this is to load all types that inherit from IRepository<T> into the IoC container in the application start event in the Global.asax:

// Register repositories - this will add all repositories except the main generic Repository<T> class
var repositoryTypes = typeof(IRepository<>).Assembly.GetTypes();
foreach (var type in repositoryTypes)
{
    if (type.IsDerivedFromGenericType(typeof(IRepository<>)) && !type.IsAbstract)
    {
        var service = type.GetInterface("I" + type.Name);
        if (!service.IsGenericType)
        {
            IoC.AddType(service.Name, service, type, IoC.LifeStyle.PerWebRequest);
        }
    }
}

This will automatically register any repositories I create with the IoC container.  Since the constructor of each repository takes an instance of ISession, Windsor will automatically resolve the repository correctly with the session.

So far I’ve liked the solution we came up with.  I like that we’re utilizing more features of our IoC container to really control how our objects are created and their lifestyles.  I also like that we’re adding items more dynamically instead of having a large config file.

Advertisement

4 Responses to “Managing NHibernate Sessions with IoC”

  1. [...] Managing NHibernate Sessions with IoC (Erik Peterson) [...]

  2. [...] Managing NHibernate Sessions with IoC (Erik Peterson) [...]

  3. [...] 29, 2010 When I wrote my last post, I added the caveat that this was not tested with handling multiple NHibernate sessions.  Sure [...]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.