NHibernate problems with a session-per-request MVC appli

5.8k views Asked by At

I've written a C# MVC 3 with NHibernate as the ORM and I'm having some odd exceptions thrown on most page loads. They seem to mostly relate to closed sessions and the like and I've checked most of the common issues but found little to help. Some of the exceptions include:

    [[NHibernate.Util.ADOExceptionReporter]] : System.InvalidOperationException: There is already an open DataReader associated with this Command which must be closed first.
   at System.Data.SqlClient.SqlInternalConnectionTds.ValidateConnectionForExecute(SqlCommand command)

    [[NHibernate.Transaction.AdoTransaction]] : Begin transaction failed
    System.Data.SqlClient.SqlException (0x80131904): The server failed to resume the transaction. Desc:3b00000006.

    [[NHibernate.Transaction.AdoTransaction]] : Commit failed
    System.NullReferenceException: Object reference not set to an instance of an object.

    [[NHibernate.Transaction.AdoTransaction]] : Commit failed
    System.Data.SqlClient.SqlException (0x80131904): The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.

    [[NHibernate.Util.ADOExceptionReporter]] : System.InvalidOperationException: The transaction is either not associated with the current connection or has been completed.
       at System.Data.SqlClient.SqlCommand.ValidateCommand(String method, Boolean async)

    [[NHibernate.Transaction.AdoTransaction]] : Begin transaction failed
    System.InvalidOperationException: SqlConnection does not support parallel transactions.

I apologise for the wall of exceptions, I suspect they are related but there could potentially be another error in the code as well causing one or two. I don't like to use the word random for these things but I can't seem to track down any specific line of code that calls them, they just seem to appear at lines of code relating to ISession objects. I have even had a "Session is closed" exception thrown on the BeginTranscation method in my Global.asax file.

The application uses the web option of current_session_context_class in hibernate.cfg.xml.

My suspicion is that it is related to my session management code. The website usually loads around 10 simultaneous AJAX requests and the errors seem to occur more often when multiple pages are loading at the same time. There are two session factories, one for each database being used.

Here is my relevant Global.asax code:

protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler.Initialize();
            RegisterGlobalFilters(GlobalFilters.Filters);
            RegisterRoutes(RouteTable.Routes);
            _sessionFactory = (new WebClientSessionManager()).MakeSessionFactory();
            _sessionFactoryNotWeb = ClientSessionManager.MakeSessionFactory();
        }
protected void Application_BeginRequest(object sender, EventArgs e)
        {
            _session = _sessionFactory.OpenSession();
            _sessionNotWeb = _sessionFactoryNotWeb.OpenSession();
            CurrentSessionContext.Bind(_sessionNotWeb);
            CurrentSessionContext.Bind(_session);
            _session.BeginTransaction();
            _sessionNotWeb.BeginTransaction();
        }
 protected void Application_EndRequest(object sender, EventArgs e)
        {
            //Same code is repeated for the _sessionFactoryNotWeb
            ISession session = CurrentSessionContext.Unbind(_sessionFactory);
            if (session != null)
            {
                if (session.Transaction.IsActive)
                {
                    try
                    {
                        session.Transaction.Commit();
                    }
                    catch
                    {
                        session.Transaction.Rollback();
                    }
                }
                try
                {
                    session.Dispose();
                }
                catch
                {

                }
            }

I have had a look at the page running in NHibernate profiler. Sometimes sessions are not started with BeginTranscation, sometimes they are not Committed, sometimes neither; and most puzzlingly, sometimes they are started three times but not finished.

Any calls to the ISession object are managed through this code (there is one for each factory):

public static ISession WebSession()
        {
            if (CurrentSessionContext.HasBind(MvcApplication._sessionFactory))
            {
                if (MvcApplication._sessionFactory.GetCurrentSession().IsOpen)
                {
                    return MvcApplication._sessionFactory.GetCurrentSession();
                }
                else
                {
                    log4net.LogManager.GetLogger(typeof(DBHandler)).Debug("Unbinding NHibernate session");
                    CurrentSessionContext.Unbind(MvcApplication._sessionFactory);
                    return WebSession();
                }
            }
            else
            {
                log4net.LogManager.GetLogger(typeof(DBHandler)).Debug("Initialising NHibernate session");
                var session = MvcApplication._sessionFactory.OpenSession();
                CurrentSessionContext.Bind(session);
                session.BeginTransaction();
                return session;
            }
        }

There are no calls to BeginTransaction or Commit throughout the app without them being flushed, committed, the session disposed of and then being reopened as per the code above. Any light you guys could shed on this would be much appreciated!

1

There are 1 answers

3
Dirk Trilsbeek On BEST ANSWER

You seem to store your session in a global (application wide) property in your Global.asax.cs. That means, the property contains the last session created, not the session for a specific request. Two requests at the same time and you don't even know if you still access the session you just created, because it might already have been overwritten by another session from the next request, running the same code. You shouldn't store your session somewhere unrelated to the web request if you want to follow a Session-per-Request pattern. You could for instance store your NH session in the HttpContext.Items-Collection. As another way to session management in MVC Ayende posted a nice example on how to wrap session management around a single MVC action instead of the whole request.