为什么NHibernate的共享跨多个请求的会议在我的MVC应用程序? [英] Why does Nhibernate share the session across multiple requests in my MVC application?

查看:170
本文介绍了为什么NHibernate的共享跨多个请求的会议在我的MVC应用程序?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们有通过StructureMap构建NHibernate的依赖条件像这样的MVC项目

  VAR SessionFactory的= ConnectionRegistry.CreateSessionFactory< NHibernate.Context.WebSessionContext>();
对于< ISessionFactory方式>()辛格尔顿()使用(SessionFactory的);
对于与所述; INHibernateSessionManager>()单例()使用<。NHibernateWebSessionManager>();

该ConnectionRegistry.CreateSessionFactory看起来像这样

 公共静态ISessionFactory CreateSessionFactory< T>()其中T:ICurrentSessionContext
        {
            如果(_sessionFactory == NULL)
            {
                锁定(_SyncLock)
                {
                    如果(_sessionFactory == NULL)
                    {
                        VAR CFG = Fluently.Configure()
                            .Database(MsSqlConfiguration.MsSql2005.ConnectionString(DataFactory.ConnectionString))
                            .CurrentSessionContext< T>()
                            .Mappings(M = GT; m.FluentMappings.AddFromAssemblyOf< IInstanceFactory>())
                            .ExposeConfiguration(C => c.SetProperty(generate_statistics,真))
                            .ExposeConfiguration(C => c.SetProperty(sql_exception_converter的typeof(SqlServerExceptionConverter).AssemblyQualifiedName));                        尝试
                        {
                            _sessionFactory = cfg.BuildSessionFactory();
                        }
                        赶上(异常前)
                        {
                            Debug.Write(错误加载流利的映射:+ EX);
                            扔;
                        }
                    }
                }
            }            返回_sessionFactory;
        }

NHibernateWebSessionManager看起来像这样

 公开的Isession会议
        {
            得到
            {
                返回的openSession();
            }
        }公众的ISession的openSession()
        {
            如果(CurrentSessionContext.HasBind(SessionFactory的))
            _currentSession = SessionFactory.getCurrentSession()函数;
            其他
            {
                _currentSession = SessionFactory.OpenSession();
                CurrentSessionContext.Bind(_currentSession);
            }
            返回_currentSession;
        }        公共无效CloseSession()
        {
            如果(_currentSession == NULL)回报;
            如果收益率(CurrentSessionContext.HasBind(SessionFactory的)!);
            _currentSession = CurrentSessionContext.Unbind(SessionFactory的);
            _currentSession.Dispose();
            _currentSession = NULL;
        }

在Application_EndRequest,我们做到这一点。

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

我们的控制器是持久性无关,行动叫唤来查询,因为他们是SessionManager注入模型提供者或命令处理器和管理自己的事务。

例如:

 公众的ActionResult EditDetails(SiteDetailsEditViewModel模型)
{
    _commandProcessor.Process(新SiteEditCommand {//映射}    //重定向
}

在CommandProcessor:

 公共无效过程(SiteEditCommand命令)
        {
            使用(VAR TRAN = _session.BeginTransaction())
            {
                VAR网站= _session.Get< D​​eliveryPoint>(command.Id);
                site.SiteName = command.Name;
                //更多的映射
                tran.Commit();
            }
        }

我们也有记录访问每个控制器动作的ActionFilter属性。

 公共无效OnActionExecuted(ActionExecutedContext filterContext)
{
    SessionLogger.LogUserActionSummary(会话,_userActionType);
}

该SessionLogger还管理从注入的是SessionManager自己的事务。

 公共无效LogUserActionSummary(INT的sessionId,串userActionTypeDescription)
        {            使用(VAR TX = _session.BeginTransaction())
            {
                //获取活动总结
                _session.Save(userActivitySummary);
                tx.Commit();
            }
        }

直到我有两个浏览器访问应用程序所有这一切工作正常。
在这种情况下抛出间歇性错误,因为(NHibernate的)会话关闭。
NHProfiler显示来自CommandProcessor方法和SessionLogger方法创建的SQL语句
从同一事务中同时浏览器会话。

如何能发生这种情况给出的WebSessionContext范围是什么?
我也试着通过structureMap设置是SessionManager到HybridHttpOrThreadLocalScoped的范围。


解决方案

问题是一个单独的组合:

<$p$p><$c$c>For<INHibernateSessionManager>().Singleton().Use<NHibernateWebSessionManager>();

这是一个不同的范围有引用一个对象(WebRequest的上下文)

  _currentSession = SessionFactory.getCurrentSession()函数;

这canot在多线程环境下正常运行(如在两个并发的浏览器访问它的情况下提及)。首先请求可能已经强制设置,即使是第二个使用的字段 _currentSession 中,然后(一会儿)。第一个 Application_EndRequest 将其关闭......而持久的人会重新创建它...

在NHibernate的范围依赖,完全按照它:

 返回SessionFactory.getCurrentSession()函数; //内部范围的处理

SessionManager.Open()

 公开的ISession的openSession()
{
  如果(CurrentSessionContext.HasBind(SessionFactory的))
  {
     返回SessionFactory.getCurrentSession()函数;
  }
  //其他
  VAR会话= SessionFactory.OpenSession();
  NHibernate.Context.CurrentSessionContext.Bind(会话);
  返回会议;
}

然后甚至单返回正确的情况下,应该工作。但是,对于一个的是SessionManager 的我会使用 HybridHttpOrThreadLocalScoped 反正。

 对少于INHibernateSessionManager&GT;()
  .HybridHttpOrThreadLocalScoped()
  。使用&LT; NHibernateWebSessionManager&GT;();

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.

解决方案

The problem is combination of a singleton:

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

Having reference to an object from a different scope (webrequest context)

_currentSession = SessionFactory.GetCurrentSession();

This canot work properly in multithread environment (as mentioned in cases of two concurrent browsers accessing it). First request could already force to set the field _currentSession, which is then (for a while) used even for the second one. The first Application_EndRequest will close it ... and lasting one will recreate it...

When relying on NHibernate scopes, follow it fully:

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

Then even singleton returning correct instances should work. But for a SessionManager I would use HybridHttpOrThreadLocalScoped anyway.

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

这篇关于为什么NHibernate的共享跨多个请求的会议在我的MVC应用程序?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆