我已经使用 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

要么打开数据读取器,要么没有打开数据读取器,但这是罪魁祸首。

我已将会话管理课程放在下面,任何人都可以找出为什么我可能会遇到这些问题吗?

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 被多次触发。我认为该事件只应该在请求的最后触发一次,但如果不是,并且其他一些项目正在尝试使用会话(这就是错误所抱怨的),无论出于什么奇怪的原因,都可能会触发是我的问题,会话仍然是线程安全的,只是被提前处理掉了。

有人有什么想法吗?我进行了谷歌搜索,发现 VS 开发服务器确实会导致类似的问题,但我是通过 IIS 运行它的。

有帮助吗?

解决方案

虽然我还没有看到您的整个代码库或您试图解决的问题,但重新思考如何使用 NHibernate 可能是合适的。来自 文档:

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

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

  • 创建时要格外小心 每个数据库有多个 ISession 每笔交易。ISession 本身 跟踪对加载的更新 对象,因此不同的 ISession 可能会 查看过时数据。

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

最后一点与我所说的最相关(在多线程环境中也很重要)。ISession 应该用于一个小的原子操作一次,然后就被释放。还来自文档:

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

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

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

基本上,与其试图尽可能长时间地保留会话,不如尝试尽快摆脱​​它们,看看是否有更好的结果。

编辑:

好的,我明白你想做什么。听起来您正在尝试实现“在视图中打开会话”模式,并且您可以采取几种不同的路线:

如果添加另一个框架不是问题,请查看类似的内容 Spring.NET. 。它是模块化的,因此您不必使用整个东西,您可以只使用 NHibernate 帮助程序模块。它支持视图模式中的打开会话。文档 这里 (品目 21.2.10。“网络会话管理”)。

如果您想自己动手,请查看 Bill McCafferty 发布的代码项目: 《NHibernate 最佳实践》. 。最后,他描述了通过自定义 IHttpModule 实现该模式。我还在互联网上看到过一些关于在没有 IHttpModule 的情况下实现该模式的帖子,但这可能正是您一直在尝试的。

我通常的模式(也许您已经在这里跳过了)是首先使用框架。它消除了很多头痛。如果它太慢或不符合我的需求,那么我会尝试调整配置或自定义它。只有在那之后我才尝试推出自己的,但是YMMV。:)

其他提示

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

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

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

希望这会有所帮助。 加雷

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top