Asp.net

帶會話的 NHibernate 執行緒安全

  • December 28, 2020

我已經使用 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 被多次觸發。我認為該事件只假設在請求結束時觸發一次,但如果不是,並且其他一些項目正在嘗試使用 Session (這是錯誤所抱怨的),無論出於何種奇怪的原因可能是我的問題,會話仍然是執行緒安全的,它只是被提前處理掉了。

有人有什麼想法嗎?我做了一個Google,看到 VS 開發伺服器確實會導致這樣的問題,但我是通過 IIS 執行它。

雖然我還沒有看到你的整個程式碼庫或你試圖解決的問題,但重新思考你如何使用 NHibernate 可能是正確的。從文件中:

在創建 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 輔助模組。它支持視圖模式中的打開會話。此處的文件(標題 21.2.10。“Web 會話管理”)。

如果您想自己動手,請查看 Bill McCafferty 發布的這個程式碼項目:“NHibernate Best Practices”。最後,他描述了通過自定義 IHttpModule 實現該模式。我還在 Internet 上看到了關於在沒有 IHttpModule 的情況下實現該模式的文章,但這可能是您一直在嘗試的。

我通常的模式(也許你已經在這裡跳過了)首先使用框架。它消除了很多頭痛。如果它太慢或不符合我的需求,那麼我會嘗試調整配置或自定義它。只有在那之後我才嘗試推出自己的,但 YMMV。:)

引用自:https://stackoverflow.com/questions/629719