Simple NHibernate Example, Part 4 : Session Management


Before continuing with the implementation, let’s talk briefly about NHibernate session management. This will be a short discussion, as you can get more details from chapter 8 of the book, Hibernate In Action. Also, please review the article, NHibernate Best Practices, for further explanation of the Session In View.

One of the potential problems with using NHibernate is the intermingling of managing the NHibernate sessions and transactions with the basic interactions between the domain and NHibernate. Ideally, we’d like the client using NHibernate to manage transactions outside of the context of the business workflow, committing or rolling back entire transactions depending on the results of the workflow (this is the unit of work pattern).

We’d like to be able to manage a single session across a single persistence context (or transaction context). We’ll utilize the thread safe CallContext object in an NHibernate SessionManager class. An upcoming post will show how this session manager works. In the meantime, here is the code (borrowed from NHibernate Best Practices):

using System.Configuration;

using System.Runtime.Remoting.Messaging;

using System.Web;

using NHibernate;

using NHibernate.Cache;

using Configuration=NHibernate.Cfg.Configuration;

using NHibernateConfiguration = NHibernate.Cfg;

 

namespace DealerMatrix.Data.NHibernate.Session

{

    ///

</span> </p>

    /// Handles creation and management of sessions and transactions.  It is a singleton because

    /// building the initial session factory is very expensive. Inspiration for this class came

    /// from Chapter 8 of Hibernate in Action by Bauer and King.  Although it is a sealed singleton

    /// you can use TypeMock (http://www.typemock.com) for more flexible testing.

    /// </summary>

    public sealed class NHibernateSessionManager

    {

        private ISessionFactory sessionFactory;

 

        #region Thread-safe, lazy Singleton

 

        ///

</span> </p>

        /// This is a thread-safe, lazy singleton.  See http://www.yoda.arachsys.com/csharp/singleton.html

        /// for more details about its implementation.

        /// </summary>

        public static NHibernateSessionManager Instance

        {

            get { return Nested.nHibernateNHibernateSessionManager; }

        }

 

        ///

</span> </p>

        /// Initializes the NHibernate session factory upon instantiation.

        /// </summary>

        private NHibernateSessionManager()

        {

            InitSessionFactory();

        }

 

        ///

</span> </p>

        /// Assists with ensuring thread-safe, lazy singleton

        /// </summary>

        private class Nested

        {

            static Nested()

            {

            }

 

            internal static readonly NHibernateSessionManager nHibernateNHibernateSessionManager = new NHibernateSessionManager();

        }

 

        #endregion

 

        private void InitSessionFactory()

        {

            Configuration cfg = new Configuration();

 

            // The following makes sure the the web.config contains a declaration for the HBM_ASSEMBLY appSetting

            if (ConfigurationManager.AppSettings[“HBM_ASSEMBLY”] == null ||

                ConfigurationManager.AppSettings[“HBM_ASSEMBLY”] == “”)

            {

                throw new ConfigurationErrorsException(“NHibernateManager.InitSessionFactory: “HBM_ASSEMBLY” must be “ +

                                                       “provided as an appSetting within your config file. “HBM_ASSEMBLY” informs NHibernate which assembly “ +

                                                       “contains the HBM files. It is assumed that the HBM files are embedded resources. An example config “ +

                                                       “declaration is <add key=”HBM_ASSEMBLY” value=”MyProject.Core” />”);

            }

 

            cfg.AddAssembly(ConfigurationManager.AppSettings[“HBM_ASSEMBLY”]);

            sessionFactory = cfg.BuildSessionFactory();

        }

 

        ///

</span> </p>

        /// Allows you to register an interceptor on a new session.  This may not be called if there is already

        /// an open session attached to the HttpContext.  If you have an interceptor to be used, modify

        /// the HttpModule to call this before calling BeginTransaction().

        /// </summary>

        public void RegisterInterceptor(IInterceptor interceptor)

        {

            ISession session = threadSession;

 

            if (session != null && session.IsOpen)

            {

                throw new CacheException(“You cannot register an interceptor once a session has already been opened”);

            }

 

            GetSession(interceptor);

        }

 

        public ISession GetSession()

        {

            return GetSession(null);

        }

 

        ///

</span> </p>

        /// Gets a session with or without an interceptor.  This method is not called directly; instead,

        /// it gets invoked from other public methods.

        /// </summary>

        private ISession GetSession(IInterceptor interceptor)

        {

            ISession session = threadSession;

 

            if (session == null)

            {

                if (interceptor != null)

                {

                    session = sessionFactory.OpenSession(interceptor);

                }

                else

                {

                    session = sessionFactory.OpenSession();

                }

 

                threadSession = session;

            }

 

            return session;

        }

 

        public void CloseSession()

        {

            ISession session = threadSession;

            threadSession = null;

 

            if (session != null && session.IsOpen)

            {

                session.Close();

            }

        }

 

        public void BeginTransaction()

        {

            ITransaction transaction = threadTransaction;

 

            if (transaction == null)

            {

                transaction = GetSession().BeginTransaction();

                threadTransaction = transaction;

            }

        }

 

        public void CommitTransaction()

        {

            ITransaction transaction = threadTransaction;

 

            try

            {

                if (transaction != null && !transaction.WasCommitted && !transaction.WasRolledBack)

                {

                    transaction.Commit();

                    threadTransaction = null;

                }

            }

            catch (HibernateException ex)

            {

                RollbackTransaction();

                throw ex;

            }

        }

 

        public void RollbackTransaction()

        {

            ITransaction transaction = threadTransaction;

 

            try

            {

                threadTransaction = null;

 

                if (transaction != null && !transaction.WasCommitted && !transaction.WasRolledBack)

                {

                    transaction.Rollback();

                }

            }

            catch (HibernateException ex)

            {

                throw ex;

            }

            finally

            {

                CloseSession();

            }

        }

 

        ///

</span> </p>

        /// If within a web context, this uses <see cref=”HttpContext” /> instead of the WinForms

        /// specific <see cref=”CallContext” />.  Discussion concerning this found at

        /// http://forum.springframework.net/showthread.php?t=572.

        /// </summary>

        private ITransaction ThreadTransaction

        {

            get

            {

                if (IsInWebContext())

                {

                    return (ITransaction) HttpContext.Current.Items[TRANSACTION_KEY];

                }

                else

                {

                    return (ITransaction) CallContext.GetData(TRANSACTION_KEY);

                }

            }

            set

            {

                if (IsInWebContext())

                {

                    HttpContext.Current.Items[TRANSACTION_KEY] = value;

                }

                else

                {

                    CallContext.SetData(TRANSACTION_KEY, value);

                }

            }

        }

 

        ///

</span> </p>

        /// If within a web context, this uses <see cref=”HttpContext” /> instead of the WinForms

        /// specific <see cref=”CallContext” />.  Discussion concerning this found at

        /// http://forum.springframework.net/showthread.php?t=572.

        /// </summary>

        private ISession ThreadSession

        {

            get

            {

                if (IsInWebContext())

                {

                    return (ISession) HttpContext.Current.Items[SESSION_KEY];

                }

                else

                {

                    return (ISession) CallContext.GetData(SESSION_KEY);

                }

            }

            set

            {

                if (IsInWebContext())

                {

                    HttpContext.Current.Items[SESSION_KEY] = value;

                }

                else

                {

                    CallContext.SetData(SESSION_KEY, value);

                }

            }

        }

 

        private static bool IsInWebContext()

        {

            return HttpContext.Current != null;

        }

 

        private const string TRANSACTION_KEY = “CONTEXT_TRANSACTION”;

        private const string SESSION_KEY = “CONTEXT_SESSION”;

    }

}

</div> * Have a look at the last two properties to see how the CallContext is used. These maintain the transaction and session across a single request thread. * InitSessionFactory() requires that HBM_ASSEMBLY be defined in the config file. * Finally, be sure to call CommitTransaction(), RollbackTransaction() and/or CloseSession() at the end of the request to clear out the CallContext at the end of the thread request. Again, I will show you how to put all these pieces together in an upcoming post.

Using NHibernate 1.2 with Monorail