为什么Nhibernate在我的MVC应用程序中跨多个请求共享会话



我们有一个MVC项目,通过类似的StructureMap构建NHibernate依赖项

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

ConnectionRegistry.CreateSessionFactory看起来像这个

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看起来像这个

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

在Application_EndRequest中,我们执行

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

我们的控制器是持久性不可知的,操作调用查询模型提供程序或命令处理程序,这些程序注入了sessionManager并管理自己的事务。

例如:

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

在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();
}
}

我们还有一个ActionFilter属性,记录对每个控制器操作的访问。

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

SessionLogger还通过注入的SessionManager 管理自己的事务

public void LogUserActionSummary(int sessionId, string userActionTypeDescription)
{
using (var tx = _session.BeginTransaction())
{
//get activity summary
_session.Save(userActivitySummary);
tx.Commit();
}
}

所有这些都很好,直到我有两个浏览器访问该应用程序。在这种情况下,由于(NHibernate)会话已关闭,会引发间歇性错误。NHProfiler显示从CommandProcessor方法和SessionLogger方法创建的SQL语句来自同一事务中的两个浏览器会话。

给定WebSessionContext范围,这怎么可能发生?我还尝试通过structureMap将sessionManager的作用域设置为HybridHttpOrThreadLocalScoped。

问题是单例的组合:

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

引用来自不同范围(web请求上下文)的对象

_currentSession = SessionFactory.GetCurrentSession();

这在多线程环境中无法正常工作(如两个并发浏览器访问它的情况所述)。第一个请求可能已经强制设置字段_currentSession,然后(在一段时间内)甚至用于第二个请求。第一个Application_EndRequest将关闭它。。。持久的一个会重现它…

当依赖NHibernate示波器时,请完全遵循:

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

然后,即使是单例返回正确的实例也应该起作用。但是对于SessionManager,我无论如何都会使用HybridHttpOrThreadLocalScoped

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

最新更新