NHibernate线程安全与会话

时间:2009-03-10 11:18:22

标签: asp.net nhibernate thread-safety httpcontext

我一直在使用NHibernate一段时间,并且不时发现如果我尝试同时请求两个页面(或尽可能接近),它偶尔会出错。所以我认为这是因为我的会话管理不是线程安全的。

我认为这是我的课程,所以我尝试使用此博客http://pwigle.wordpress.com/2008/11/21/nhibernate-session-handling-in-aspnet-the-easy-way/中的其他方法,但我仍然遇到同样的问题。我得到的实际错误是:

Server Error in '/AvvioCMS' Application.
failed to lazily initialize a collection, no session or session was closed
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: NHibernate.LazyInitializationException: failed to lazily initialize a collection, no session or session was closed

是否打开了datareader,但这是罪魁祸首。

我已将会话管理课程放在下面,有人能说出为什么我会遇到这些问题吗?

public interface IUnitOfWorkDataStore
{
    object this[string key] { get; set; }
}


    public static Configuration Init(IUnitOfWorkDataStore storage, Assembly[] assemblies)
    {
        if (storage == null)
            throw new Exception("storage mechanism was null but must be provided");

        Configuration cfg = ConfigureNHibernate(string.Empty);
        foreach (Assembly assembly in assemblies)
        {
            cfg.AddMappingsFromAssembly(assembly);
        }

        SessionFactory = cfg.BuildSessionFactory();
        ContextDataStore = storage;

        return cfg;
    }

    public static ISessionFactory SessionFactory { get; set; }
    public static ISession StoredSession
    {
        get
        {
            return (ISession)ContextDataStore[NHibernateSession.CDS_NHibernateSession];
        }
        set
        {
            ContextDataStore[NHibernateSession.CDS_NHibernateSession] = value;
        }
    }

    public const string CDS_NHibernateSession = "NHibernateSession";
    public const string CDS_IDbConnection = "IDbConnection";

    public static IUnitOfWorkDataStore ContextDataStore { get; set; }

    private static object locker = new object();
    public static ISession Current 
    {
        get 
        {
            ISession session = StoredSession;

            if (session == null) 
            {
                lock (locker)
                {
                    if (DBConnection != null)
                        session = SessionFactory.OpenSession(DBConnection);
                    else
                        session = SessionFactory.OpenSession();

                    StoredSession = session;
                }
            }

            return session;
        }
        set
        {
            StoredSession = value;
        }
    }

    public static IDbConnection DBConnection
    {
        get
        {
            return (IDbConnection)ContextDataStore[NHibernateSession.CDS_IDbConnection];
        }
        set
        {
            ContextDataStore[NHibernateSession.CDS_IDbConnection] = value;
        }
    }

}

我使用的实际商店是:

public class HttpContextDataStore : IUnitOfWorkDataStore
{
    public object this[string key]
    {
        get { return HttpContext.Current.Items[key]; }
        set { HttpContext.Current.Items[key] = value; }
    }
}

我使用以下命令初始化Application_Start上的SessionFactory:

NHibernateSession.Init(new HttpContextDataStore(), new Assembly[] { 
                typeof(MappedClass).Assembly});

更新

大家好,感谢您的建议,我尝试了一些不同的尝试来简化代码,但我仍然遇到同样的问题,我可能知道原因。

我根据需要创建会话,但是在我的global.asax中,我在Application_EndRequest上处理会话。但是我发现当我在加载页面结束时进行调试时,Application_EndRequest被多次触发。我认为事件只是假设在请求的最后一次触发,但如果不是,并且其他一些项目正在尝试使用Session(这是错误所抱怨的),无论出于什么奇怪的原因是我的问题,Session仍然是线程安全的,它只是被提前处理。

有人有任何想法吗?我做了谷歌,看到VS开发服务器确实导致类似的问题,但我通过IIS运行它。

3 个答案:

答案 0 :(得分:24)

虽然我还没有看到您的整个代码库或您尝试解决的问题,但重新考虑如何使用NHibernate可能是有序的。来自documentation

  

您应该遵守以下规定   创建NHibernate时的做法   会话:

     
      
  • 永远不要创建多个并发   ISession或ITransaction实例   数据库连接。

  •   
  • 创建时要特别小心   每个数据库不止一个ISession   每笔交易。 ISession本身   跟踪加载的更新   对象,所以不同的ISession可能   看到陈旧的数据。

  •   
  • ISession 线程安全!从不   访问两个相同的ISession   并发线程。一个ISession是   通常只有单个工作单元

  •   

最后一点与我所说的最相关(在多线程环境的情况下很重要)。对于小型原子操作,应该使用一次ISession然后处理。另外还有文档:

  

ISessionFactory是一个   昂贵的创建,线程安全的对象   旨在由所有人共享   应用程序线程ISession是一个   廉价,非线程安全的对象   应该使用一次,一次使用   业务流程,然后丢弃。

结合这两个想法,而不是存储ISession本身,存储会话工厂,因为这是“大”对象。然后,您可以使用类似SessionManager.GetSession()的东西作为包装器从会话存储中检索工厂并实例化会话并将其用于一个操作。

在ASP.NET应用程序的上下文中,问题也不那么明显。您正在静态确定ISession对象的范围,这意味着它在AppDomain中共享。如果在该AppDomain的生命周期内创建了两个不同的页面请求并同时执行,那么现在有两个页面(不同的线程)触及同一个安全的ISession。

基本上,不要试图尽可能长时间地保持会话,尽量尽快摆脱它们,看看你是否有更好的结果。

编辑:

好的,我可以看到你想要去哪里。听起来你正在尝试实现Open Session In View模式,你可以采用几种不同的路线:

如果添加其他框架不是问题,请查看Spring.NET之类的内容。它是模块化的,所以你不必使用整个东西,你可以只使用NHibernate助手模块。它支持视图模式中的打开会话。文件here(标题21.2.10。“网络会话管理”)。

如果您想自己推出,请查看Bill McCafferty发布的代码项目:"NHibernate Best Practices"。接下来,他描述了通过自定义IHttpModule实现模式。我也看过互联网上的帖子没有IHttpModule来实现模式,但这可能是你一直在尝试的。

我常用的模式(也许你已经在这里跳过了)是先使用框架。它消除了许多令人头疼的问题。如果它太慢或不适合我的需要,那么我尝试调整配置或自定义它。只有在那之后我才会尝试自己滚动,但是YMMV。 :)

答案 1 :(得分:2)

我无法确定(因为我是Java Hibernate的人)在NHibernate中,但在休眠中,Session对象在设计上并不是线程安全的。您应该打开和关闭会话,并且永远不要允许它超出当前线程的范围。

我确信在某个地方已经在.Net中实现了诸如“开放会话视图”之类的模式。

另一个有趣的问题是当你在会话中放入一个hibernate实体时。这里的问题是它所附加的会话将在请求结束时关闭(或应该)。如果您希望导航任何未加载的关联,则必须将实体重新附加到新的(休眠)会话。如果两个请求尝试同时执行此操作,如果您尝试将实体附加到两个会话,那么这会导致新问题。

希望这会有所帮助。 加雷

答案 2 :(得分:1)

问题最终导致我的控制反转库未正确管理在HTTP上下文中创建的对象,因此我获得了对该上下文不可用的对象的引用。这是使用Ninject 1.0,一旦我更新到Ninject 2.0(beta),问题就解决了。