As with any piece of software development, there is more than a million ways to skin a cat.  Working with NHibernate in a multi-threaded environment is no different. :)  To help with the illustration of the interaction of NHibernate and ASP.NET, I’ve created a sample application out on Google code.  Please feel free to check it out and run it locally.  The only requirement for the sample is to have a local install of SQL Server Express.

The Problem

Those of you using NHibernate in a multi-threaded environment (say ASP.NET) have probably written code to maintains an ISession open for the during of a request (worker thread) so that any component within that request that needs an ISession, can just re-use it.  Typically the solution involves some fancy work with the HttpContext.Items collection by adding and removing sessions from the HttpApplication.BeginRequest and HttpApplication.EndRequest.  All of this code is infrastructure and requires some intimate knowledge of how ASP.NET works.  For all general purposes, your code can be similar to that of CommonSessionModule.cs:

   1: public class CommonSessionModule : IHttpModule {
   2:  
   3:     public void Init(HttpApplication context) {
   4:         context.BeginRequest += context_BeginRequest;
   5:         context.EndRequest += context_EndRequest;
   6:     }
   7:  
   8:     public void Dispose() {
   9:     }
  10:  
  11:     private static void context_BeginRequest(object sender, EventArgs e) {
  12:         var application = (HttpApplication)sender;
  13:         var context = application.Context;
  14:  
  15:         var sessionBuilder = SessionBuilderFactory.CurrentBuilder;
  16:         context.Items[Constants.SessionKey] = sessionBuilder.OpenSession();
  17:     }
  18:  
  19:     private static void context_EndRequest(object sender, EventArgs e) {
  20:         var application = (HttpApplication)sender;
  21:         var context = application.Context;
  22:  
  23:         var session = context.Items[Constants.SessionKey] as ISession;
  24:         if (session != null) {
  25:             session.Flush();
  26:             session.Close();
  27:         }
  28:  
  29:         context.Items[Constants.SessionKey] = null;
  30:     }
  31: }

To make the code simpler to read, I’ve introduced a ISessionBuilder and SessionBuilderFactory to handle the interaction with NHibernate and its pieces. 

As you can see from the sample above, there is quite a lot of churn happening with the Items collection.  We have to intimately know how to add and remove the active ISession (per thread) based on some constant key.  To make matters worse, the implementation of ISessionBuilder needs to know how to pull the ‘current’ ISession out of the Items collections.  The CommonSessionBuilder.cs file shows this type of interaction:

   1: public class CommonSessionBuilder : SessionBuilderBase {
   2:     public override ISession CurrentSession {
   3:         get {
   4:             //Handle the ISession from the context
   5:             var currentContext = HttpContext.Current;
   6:             var session = currentContext.Items[Constants.SessionKey] as ISession;
   7:  
   8:             //HACK: Here to support the calling of sessions from the HttpApplication start                
   9:             if (session == null) {
  10:                 session = OpenSession();
  11:                 currentContext.Items[Constants.SessionKey] = session;
  12:             }
  13:  
  14:             return session;
  15:         }
  16:     }
  17: }

Most of the “common” (Configuration settings, ISessionFactory creation, etc) are part of the SessionBuilderBase class since those pieces are going to be needed regardless the implementation we use for the CurrentSession method.  For this implementation of CurrentSession, we have to pull the current ISession from the Items collection to give it back to the caller of the property (in this instance, the RepositoryBase class):

   1: public class RepositoryBase<TEntity> : ICRUDRepository<TEntity>
   2:     where TEntity : EntityBase, new() {
   3:  
   4:     protected readonly ISessionBuilder _sessionBuilder;
   5:  
   6:     public RepositoryBase(ISessionBuilder sessionFactory) {
   7:         _sessionBuilder = sessionFactory;
   8:     }
   9:  
  10:     #region ICRUDRepository<TEntity> Members
  11:  
  12:     public void Create(TEntity entity) {
  13:         ISession session = GetSession();
  14:         using (ITransaction transaction = session.BeginTransaction()) {
  15:             session.Save(entity);
  16:  
  17:             transaction.Commit();
  18:         }
  19:     }
  20:  
  21:     public TEntity Retrieve(Guid entityId) {
  22:         ISession session = GetSession();
  23:         ICriteria criteria = session.CreateCriteria(typeof(TEntity));
  24:         criteria.Add(Expression.Eq("Id", entityId));
  25:  
  26:         return criteria.UniqueResult<TEntity>();
  27:     }
  28:  
  29:     public IList<TEntity> RetrieveAll() {
  30:         ISession session = GetSession();
  31:         ICriteria targetObjects = session.CreateCriteria(typeof(TEntity));
  32:  
  33:         return targetObjects.List<TEntity>();
  34:     }
  35:  
  36:     public void Update(TEntity entity) {
  37:         ISession session = GetSession();
  38:  
  39:         using (ITransaction transaction = session.BeginTransaction()) {
  40:             session.Update(entity);
  41:  
  42:             transaction.Commit();
  43:         }
  44:     }
  45:  
  46:     public void Delete(TEntity entity) {
  47:         ISession session = GetSession();
  48:         using (ITransaction transaction = session.BeginTransaction()) {
  49:             session.Delete(entity);
  50:  
  51:  
  52:             transaction.Commit();
  53:         }
  54:     }
  55:  
  56:     #endregion
  57:  
  58:     protected ISession GetSession() {
  59:         return _sessionBuilder.CurrentSession;
  60:     }
  61: }

All CRUD methods do a call to the GetSession method which in turn call’s the ISessionBuilder.CurrentSession method to fetch the current ISession instance.  Again, the reason for this indirection is to allow flexibility on the way an ISession is acquired during runtime.  Since it’s not the concern of the RepositoryBase to manage the intricacies of NHibernate, having such approach here keeps the code clean and clear from any unnecessary clutter.  Always a good thing to have! :)

Hopefully by now you’re noticing that this approach doesn’t do a good job at separating concerns for the data layer.  The CommonSessionBuilder class has to have knowledge of a web context, however, it’s sole purpose is to handle pieces for data access.  So, how do we solve this approach and minimize our dependency on the web context?  We use a pretty nice (not so visible) feature of NHibernate called, Contextual Sessions.

I will address Contextual Sessions on a follow up post and show how the code changes by using this pretty awesome feature!

Keep Codin’!