4

We have an MVC project that constructs the NHibernate dependecies via StructureMap like this

var sessionFactory = ConnectionRegistry.CreateSessionFactory<NHibernate.Context.WebSessionContext>();
For<ISessionFactory>().Singleton().Use(sessionFactory);
For<INHibernateSessionManager>().Singleton().Use<NHibernateWebSessionManager>();

The ConnectionRegistry.CreateSessionFactory looks like this

public static ISessionFactory CreateSessionFactory<T>() where T : ICurrentSessionContext
        {
            if (_sessionFactory == null)
            {
                lock (_SyncLock)
                {
                    if (_sessionFactory == null)
                    {
                        var cfg = Fluently.Configure()
                            .Database(MsSqlConfiguration.MsSql2005.ConnectionString(DataFactory.ConnectionString))
                            .CurrentSessionContext<T>()
                            .Mappings(m => m.FluentMappings.AddFromAssemblyOf<IInstanceFactory>())
                            .ExposeConfiguration(c => c.SetProperty("generate_statistics", "true"))
                            .ExposeConfiguration(c => c.SetProperty("sql_exception_converter", typeof(SqlServerExceptionConverter).AssemblyQualifiedName));

                        try
                        {
                            _sessionFactory = cfg.BuildSessionFactory();
                        }
                        catch (Exception ex)
                        {
                            Debug.Write("Error loading Fluent Mappings: " + ex);
                            throw;
                        }
                    }
                }
            }

            return _sessionFactory;
        }

NHibernateWebSessionManager looks like this

public ISession Session
        {
            get
            {               
                return OpenSession();
            }
        }

public ISession OpenSession()
        {
            if(CurrentSessionContext.HasBind(SessionFactory))
            _currentSession = SessionFactory.GetCurrentSession();
            else
            {
                _currentSession = SessionFactory.OpenSession();
                CurrentSessionContext.Bind(_currentSession);
            }
            return _currentSession;
        }

        public void CloseSession()
        {
            if (_currentSession == null) return;
            if (!CurrentSessionContext.HasBind(SessionFactory)) return;
            _currentSession = CurrentSessionContext.Unbind(SessionFactory);
            _currentSession.Dispose();
            _currentSession = null;
        }

In Application_EndRequest, we do this

ObjectFactory.GetInstance<INHibernateSessionManager>().CloseSession();
ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects();

Our controllers are persistence agnostic and actions call out to query model providers or command processors which have the sessionManager injected and manage their own transactions.

For example:

public ActionResult EditDetails(SiteDetailsEditViewModel model)
{
    _commandProcessor.Process(new SiteEditCommand { //mappings }

    //redirect
}

In the CommandProcessor:

public void Process(SiteEditCommand command)
        {
            using (var tran = _session.BeginTransaction())
            {
                var site = _session.Get<DeliveryPoint>(command.Id);
                site.SiteName = command.Name;
                //more mappings
                tran.Commit();
            }
        }

We also have an ActionFilter attribute that logs access to each controller action.

public void OnActionExecuted(ActionExecutedContext filterContext)
{
    SessionLogger.LogUserActionSummary(session, _userActionType);
}

The SessionLogger also manages its own transactions from an injected SessionManager

public void LogUserActionSummary(int sessionId, string userActionTypeDescription)
        {

            using (var tx = _session.BeginTransaction())
            {
                //get activity summary
                _session.Save(userActivitySummary);
                tx.Commit();
            }
        }

All of this works fine until I have two browsers accessing the app. In this scenario intermittent errors are thrown because the (NHibernate) Session is closed. NHProfiler shows SQL statements created from both CommandProcessor methods and SessionLogger methods from both browser sessions within the same transaction.

How can this occur given the WebSessionContext scope? I've also tried setting the scope of the sessionManager to HybridHttpOrThreadLocalScoped via structureMap.

friedX
  • 327
  • 5
  • 12

1 Answers1

2

The problem is combination of a singleton:

For<INHibernateSessionManager>().Singleton().Use<NHibernateWebSessionManager>();

Having reference to an object from a different scope (webrequest context)

_currentSession = SessionFactory.GetCurrentSession();

This canot work properly in multithread environment (as mentioned in cases of two concurrent browsers accessing it). First request could already force to set the field _currentSession, which is then (for a while) used even for the second one. The first Application_EndRequest will close it ... and lasting one will recreate it...

When relying on NHibernate scopes, follow it fully:

return SessionFactory.GetCurrentSession(); // inside is the scope handled

SessionManager.Open()

public ISession OpenSession()
{
  if(CurrentSessionContext.HasBind(SessionFactory))
  {
     return SessionFactory.GetCurrentSession();
  }
  // else  
  var session = SessionFactory.OpenSession();
  NHibernate.Context.CurrentSessionContext.Bind(session);
  return session;
}

Then even singleton returning correct instances should work. But for a SessionManager I would use HybridHttpOrThreadLocalScoped anyway.

For<INHibernateSessionManager>()
  .HybridHttpOrThreadLocalScoped()
  .Use<NHibernateWebSessionManager>();
Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
  • If you look at the last line of my post you will see that I've also tried setting the scope of the sessionManager to HybridHttpOrThreadLocalScoped via structureMap. I've tried editing my code using your suggestion to return GetCurrentSession directly instead of assigning it but I get the same errors. – friedX Dec 03 '12 at 11:07
  • 1
    In that case I would try to disable the AOP filter for a while. The MVC filters are cached among requests. So if there is an instance, referencing some SessionManager (with its private fields) it will be used for more then one request. here is more about that http://www.asp.net/whitepapers/mvc3-release-notes#RTM-BC – Radim Köhler Dec 03 '12 at 11:28
  • Well that would certainly explain a lot since the error disappears when I comment out the action filter - I assumed that it was just the frequency of the action filter calls that highlighted the error. However I am now calling SessionManager.OpenSession() directly as per your suggestion so I am not sure what reference the action filter is hanging on to. – friedX Dec 03 '12 at 12:15
  • Yes, it is not easy to do DAO operations in AOP. If this must be done this way (I am avoding it using log4Net and writing to files), you should try to create separated Session, only for AOP method. **This is very important**, becuase you **most likely need to log stuff**, even if the **Rollback()** for entity handling operation is called. So if you have to use NHibernate for that, create separated session (OpenSession() and close it immediately) – Radim Köhler Dec 03 '12 at 12:23
  • Hi RadimKohler . I am facing a similar problem in my set up. In case you are not busy can you please comment on this question http://stackoverflow.com/questions/16919579/does-asp-net-multithreading-impact-structuremap-singleton-class – ganeshran Jun 04 '13 at 14:42