c# – 为什么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>();

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

_currentSession = SessionFactory.GetCurrentSession();

这个canot在多线程环境中正常工作(如两个并发浏览器访问它时所述).第一个请求可能已强制设置字段_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>();
点赞