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.