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

时间:2012-12-03 09:30:06

标签: c# asp.net-mvc nhibernate fluent-nhibernate structuremap

我们有一个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。

1 个答案:

答案 0 :(得分:2)

问题是单身人士的组合:

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

引用来自不同范围的对象(webrequest context)

_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>();