4

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

在命令处理器中:

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。

4

1 回答 1

2

问题是单例的组合:

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

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

_currentSession = SessionFactory.GetCurrentSession();

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

当依赖 NHibernate 作用域时,完全遵循它:

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

会话管理器.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>();
于 2012-12-03T10:31:25.230 回答