NHibernate Session-Per-Request with ASP.NET MVC

UPDATE: Don't use my SessionPerRequest filter. Apparently NHibernate should always have an explicit transaction. For the record, Sharp Architecture disagrees so maybe the jury is still out?

Session-per-request is an ORM pattern that means a new session is created for each request and terminated at the end of the request. I have also implemented a transaction-per-request option, by which I mean that a transaction is created for each request and committed (or rolled back) at the end of the request. Using transaction-per-request I can read, insert update and delete as much as I like during the action execution and at the end NHibernate will synchronize the changes to the database.

Because I am a big fan of simplicity I have chosen to implement these patterns using plain Asp.Net MVC action filters. This is actually not a bad approach because the choice between readonly or persistent session must be made for each request anyway, therefore there is no point hiding this decision.

Here is my Session-Per-Request implementation:

    /// <summary>Use this filter for read-only actions.</summary>
    public class SessionPerRequest : ActionFilterAttribute
    {
        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            // use this method instead of OnResultExecuted if the session does not
            // need to be open for the view rendering.
        }

        public override void OnResultExecuted(ResultExecutedContext filterContext)
        {
            NHibernateHelper.DisposeSession();
        }
    }

This filter simply handles the disposal of the session at the end of the request. I do not have to explicitly open the session because this is handled elsewhere the first time that the session is accessed. This filter can be used like this:

        [SessionPerRequest]
        public ActionResult Index()
        {
            // readonly code here
            return View();
        }

For actions that need to persist state changes a different filter is required. The TransactionPerRequest filter opens a transaction before the action is executed and commits the transaction when the view has been rendered.

    /// <summary>
    /// Use this filter for actions that need to persist state.
    /// </summary>
    public class TransactionPerRequest : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            NHibernateHelper.BeginTransaction();
        }

        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            // use this method instead of OnResultExecuted if the transaction / session 
            // does not need be open for the view rendering.
        }

        public override void OnResultExecuted(ResultExecutedContext filterContext)
        {
            if (filterContext.Exception == null)
                NHibernateHelper.CommitTransaction();
            
            NHibernateHelper.DisposeSession();
        }
    }

Usage of TransactionPerRequest is the same as for SessionPerRequest. Both of these action filters defer NHibernate access to a class called NHibernateHelper. NHibernate is a simple wrapper that manages access to an ISessionFactory singleton, and instances of ISession.

    public class NHibernateHelper
    {
        private static ISessionFactory sessionFactory;

        /// <summary>
        /// SessionFactory is static because it is expensive to create and is therefore at application scope.
        /// The property exists to provide 'instantiate on first use' behaviour.
        /// </summary>
        private static ISessionFactory SessionFactory
        {
            get
            {
                if (sessionFactory == null)
                {
                    sessionFactory = new Configuration().Configure().AddAssembly(typeof(Models.Store).Assembly).BuildSessionFactory();
                }
                return sessionFactory;
            }
        }

        public static ISession GetCurrentSession()
        {
            if (!CurrentSessionContext.HasBind(SessionFactory))
            {
                CurrentSessionContext.Bind(SessionFactory.OpenSession());
            }
            return SessionFactory.GetCurrentSession();
        }

        public static void DisposeSession()
        {
            var session = GetCurrentSession();
            session.Close();
            session.Dispose();
        }

        public static void BeginTransaction()
        {
            GetCurrentSession().BeginTransaction();
        }

        public static void CommitTransaction()
        {
            var session = GetCurrentSession();
            if (session.Transaction.IsActive)
                session.Transaction.Commit();
        }

        public static void RollbackTransaction()
        {
            var session = GetCurrentSession();
            if (session.Transaction.IsActive)
                session.Transaction.Rollback();
        }
    }

The GetCurrentSession method ensures that an existing session is returned if one exists, otherwise a new session is created. By using NHibernate's CurrentSessionContext singleton the scoping of sessions is controlled by the NHibernate current_session_context_class property. In my web application I configure the property like this:

        web

In my test project I will change the value of the current_session_context_class property to call so that NHibernateHelper can function without a HttpContext.

I am sure there are better ways to do what I am trying to achieve, so if you see any areas for improvement please leave a comment.

 

kick it on DotNetKicks.com


Comments

Comments are closed