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

{

    /// <summary>

    /// 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

 

        /// <summary>

        /// 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; }

        }

 

        /// <summary>

        /// Initializes the NHibernate session factory upon instantiation.

        /// </summary>

        private NHibernateSessionManager()

        {

            InitSessionFactory();

        }

 

        /// <summary>

        /// 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();

        }

 

        /// <summary>

        /// 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);

        }

 

        /// <summary>

        /// 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();

            }

        }

 

        /// <summary>

        /// 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);

                }

            }

        }

 

        /// <summary>

        /// 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”;

    }

}

  • 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.

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Nelson Montalvo

I’m a software developer who loves .NET, Agile methodologies, Test Driven Design, Domain Driven techniques, and open source tools. Don’t get me wrong, I also like to use a thing or two that Microsoft creates besides .NET itself. :) In my “spare time” (when is that, anyways?) I like riding my motorcycle (http://sportbike.nmonta.com), reading, watching movies (lots of movies), working out, and hanging out (good conversation, good beer, good times). Look me up in Facebook, Linked In or Plaxo. My personal code blog is http://codemonkey.nmonta.com.
This entry was posted in NHibernate. Bookmark the permalink. Follow any comments here with the RSS feed for this post.

3 Responses to Simple NHibernate Example, Part 4 : Session Management

  1. jlockwood says:

    Hey Nellie, same issue as you other post regarding CallContext. I found a forum thread by Erich Eichinger with a link to a detailed atricle that may be worth reviewing here.

  2. frederik says:

    ‘ve been reading Billy McCafferty’s article as well, and it is indeed a very good article.
    The way of working using the ‘NHibernateSessionMgr’ and using the ‘Open Session in View’ pattern is perfectly usable in an ASP.NET application, but imho it is not a good solution in a WinForms app.
    IMHO, it’s a bit problematic to use this way of working in a WinForms app, since it is impossible to have 2 UoW’s (in this case open ISession’s) at the same time.
    Consider this scenario:
    - You have a form that lists all customers. This form has an edit button. Clicking on the edit button opens a detail form where you can change the details of a Customer.
    - Now, if you open 2 different customer-detail forms, they’ll share the same ISession. How will you cope with that ? When you close the detail-form, you’ll probably want to save the changes, and close the ISession. But, what happens then with the other detail-form ?
    - Or, what if you have another form which lists all the Orders for example ? What if you open the Customer-List form and the Orders-List form at the same time ?

    IMHO, in a WinForms scenario, you’ll want one Unit Of Work for each instance of a form.

  3. Donna says:

    It’s been 1.5 years… upcoming post still up & coming?