12

私はしばらくの間 NHibernate を使用してきましたが、2 つのページを同時に (またはできる限り近くで) 要求しようとすると、エラーが発生することがあります。したがって、セッション管理がスレッドセーフではないためだと思いました。

私のクラスだと思ったので、このブログ投稿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 が複数回発生していることがわかります。イベントはリクエストの最後に一度だけ発生すると思われますが、そうではなく、他のいくつかのアイテムがセッションを使用しようとしている場合 (これがエラーの原因です)、何らかの奇妙な理由で発生する可能性があります私の問題であり、セッションはまだスレッドセーフであり、早期に破棄されています。

誰でもアイデアはありますか?Google を実行したところ、VS 開発サーバーがそのような問題を引き起こしていることがわかりましたが、IIS を介して実行しています。

4

3 に答える 3

24

あなたのコードベース全体や解決しようとしている問題を私は見ていませんが、NHibernate の使用方法を再考する必要があるかもしれません。ドキュメントから:

NHibernate セッションを作成するときは、次のプラクティスに従う必要があります。

  • データベース接続ごとに複数の同時 ISession または ITransaction インスタンスを作成しないでください。

  • トランザクションごとにデータベースごとに複数の ISession を作成する場合は、十分に注意してください。ISession 自体は、読み込まれたオブジェクトに対して行われた更新を追跡するため、別の ISession で古いデータが表示される場合があります。

  • ISession はスレッドセーフではありません! 2 つの同時スレッドで同じ ISession にアクセスしないでください。通常、ISession は1 つの作業単位にすぎません。

最後の部分は、私が言っていることに最も関連しています (マルチスレッド環境の場合は重要です)。ISession は、小さなアトミック操作に 1 回使用してから破棄する必要があります。また、ドキュメントから:

ISessionFactory は、すべてのアプリケーション スレッドで共有されることを目的とした、作成にコストがかかるスレッドセーフなオブジェクトです。ISession は、1 つのビジネス プロセスに対して一度だけ使用し、その後は破棄する必要のある安価な非スレッド セーフ オブジェクトです。

これら 2 つのアイデアを組み合わせて、ISession 自体を格納する代わりに、セッション ファクトリを格納します。これは「大きな」オブジェクトであるためです。次に、SessionManager.GetSession() などをラッパーとして使用して、セッション ストアからファクトリを取得し、セッションをインスタンス化して、1 つの操作に使用できます。

この問題は、ASP.NET アプリケーションのコンテキストではあまり明白ではありません。ISession オブジェクトを静的にスコープしています。つまり、AppDomain 全体で共有されています。その AppDomain の有効期間内に 2 つの異なる Page リクエストが作成され、同時に実行される場合、2 つのページ (異なるスレッド) が同じ ISession にアクセスすることになり、これは安全ではありません

基本的に、セッションをできるだけ長く維持しようとするのではなく、できるだけ早くそれらを取り除き、より良い結果が得られるかどうかを確認してください.

編集:

わかりました、これでどこに行こうとしているのかがわかります。Open Session In View パターンを実装しようとしているようですが、それにはいくつかの異なるルートがあります。

別のフレームワークを追加しても問題ない場合は、Spring.NETなどを調べてください。これはモジュール式であるため、すべてを使用する必要はありません。NHibernate ヘルパー モジュールを使用するだけでかまいません。ビュー パターンでのオープン セッションをサポートします。こちらのドキュメント(見出し 21.2.10.「Web セッション管理」)。

自分で作成したい場合は、Bill McCafferty によるコードプロジェクトの投稿「NHibernate Best Practices」を確認してください。最後に、カスタム IHttpModule を使用してパターンを実装する方法について説明します。IHttpModule を使用せずにパターンを実装するためのインターネット上の投稿も見ましたが、それはあなたが試してきたことかもしれません。

私の通常のパターン (そして、おそらくここでは既にスキップされているかもしれません) は、最初にフレームワークを使用することです。それは多くの頭痛を取り除きます。遅すぎたり、自分のニーズに合わない場合は、構成を微調整したりカスタマイズしたりします。その後、私は自分自身をロールバックしようとしますが、YMMV. :)

于 2009-03-10T19:37:44.460 に答える
2

NHibernate では (私は Java Hibernate の人なので) 確かではありませんが、hibernate では Session オブジェクトは設計上スレッド セーフではありません。セッションを開いたり閉じたりする必要があり、現在のスレッドの範囲外に出ないようにしてください。

「セッション ビューを開く」などのパターンは、.Net のどこかに実装されているはずです。

もう 1 つの興味深い問題は、休止状態のエンティティをセッションに配置する場合です。ここでの問題は、接続されているセッションがリクエストの終了時に閉じられる (または閉じられるべきである) ことです。ロードされていない関連付けをナビゲートする場合は、エンティティを新しい (休止状態の) セッションに再接続する必要があります。エンティティを 2 つのセッションにアタッチしようとすると何かが爆発すると同時に、2 つのリクエストがこれを実行しようとすると、それ自体が新しい問題を引き起こします。

お役に立てれば。ガレス

于 2009-03-10T13:28:42.023 に答える
1

問題は、制御の反転用のライブラリが HTTP コンテキストで作成されたオブジェクトを正しく管理していなかったため、そのコンテキストで使用できないはずのオブジェクトの参照を取得していたことでした。これは Ninject 1.0 を使用していましたが、Ninject 2.0 (ベータ) に更新すると問題は解決しました。

于 2009-07-20T10:15:21.783 に答える