Dot-Net

如何使 NHibernate 記憶體獲取子集合?

  • May 2, 2017

我有一個相當簡單的標準查詢來獲取子集合,如下所示:

var order = Session.CreateCriteria<Order>()
   .Add(Restrictions.Eq("Id", id))
   .SetFetchMode("Customer", FetchMode.Eager)
   .SetFetchMode("Products", FetchMode.Eager)
   .SetFetchMode("Products.Category", FetchMode.Eager)
   .SetCacheable(true)
   .UniqueResult<Order>();

使用 NH Prof,我已經驗證了這只需使用冷記憶體來一次往返數據庫(如預期的那樣);但是,在連續執行時,它僅從Order記憶體中檢索 ,然後為圖中的每個子實體使用 SELECT(N+1) 訪問數據庫,如下所示:

Cached query: SELECT ... FROM Order this_ left outer join Customer customer2 [...]
SELECT ... FROM Customer WHERE Id = 123;
SELECT ... FROM Products WHERE Id = 500;
SELECT ... FROM Products WHERE Id = 501;
...
SELECT ... FROM Categories WHERE Id = 3;

等等等等。顯然,它沒有記憶體整個查詢或圖形,只記憶體根實體。第一個“記憶體查詢”行實際上具有join它應該具有的所有條件 - 它肯定是正確地記憶體查詢本身,而不是實體,顯然。

我已經嘗試過使用 SysCache、SysCache2 甚至 HashTable 記憶體提供程序,而且我似乎總是得到相同的行為(NH 版本 3.2.0)。

Google搜尋發現了許多古老的問題,例如:

但是,這些似乎都是很久以前修復的,無論我使用哪個提供程序,我都會遇到同樣的不良行為。

我已經閱讀了關於 SysCache 和 SysCache2 的 nhibernate.info 文件,似乎沒有任何我遺漏的東西。我已經嘗試為查詢中涉及的所有表添加cacheRegion行到Web.config文件中,但它不會改變任何東西(並且AFAIK這些元素只是為了使記憶體無效,所以它們無論如何都不重要)。

由於所有這些似乎都已修復/解決的超級老問題,我認為這不可能仍然是 NHibernate 中的錯誤,它一定是我做錯了。但是什麼?

將 NHibernate 中的 fetch 指令與二級記憶體相結合時,我需要做些什麼特別的事情嗎?我在這裡想念什麼?

我確實設法弄清楚了這一點,所以其他人終於可以得到一個直接的答案:

總結一下,對於二級記憶體和查詢記憶體的區別,我已經困惑了一段時間;傑森的回答在技術上是正確的,但不知何故並沒有為我點擊。這是我將如何解釋它:

  • 查詢記憶體跟踪查詢發出的實體。它不會記憶體整個結果集。這相當於Session.Load對延遲載入的實體執行 a ;它知道/期望一個存在,但除非特別要求,否則它不會跟踪任何其他資訊,此時它將實際載入真實實體。
  • 二級記憶體跟踪每個實體的實際數據。當 NHibernate 需要通過其 ID 載入任何實體時(通過Session.Load, Session.Get, 延遲載入關係,或者在上面的例子中,實體“引用”是記憶體查詢的一部分),它會在二級先記憶體。

當然,事後看來這完全有道理,但是當您聽到“查詢記憶體”和“二級記憶體”這兩個術語在很多地方幾乎可以互換使用時,這並不是那麼明顯。

本質上,您需要配置兩組,每組兩個設置,以便通過查詢記憶體查看預期結果:

1.啟用兩個記憶體

在 XML 配置中,這意味著添加以下兩行:

<property name="cache.use_second_level_cache">true</property>
<property name="cache.use_query_cache" >true</property>

在 Fluent NHibernate 中,是這樣的:

.Cache(c => c
   .UseQueryCache()
   .UseSecondLevelCache()
   .ProviderClass<SysCacheProvider>())

請注意UseSecondLevelCache上述內容,因為(在本文發佈時)Fluent NHibernate wiki 頁面上從未提及它;有幾個啟用查詢記憶體但不啟用二級記憶體的範例!

2.為每個實體啟用記憶體

僅啟用二級記憶體幾乎沒有任何作用,這就是我被絆倒的地方。二級記憶體不僅要啟用,而且要為您想要記憶體的每個單獨的實體類進行配置

在 XML 中,這是在<class>元素內部完成的:

<cache usage="read-write"/>

在 Fluent NHibernate(非自動映射)中,它是在ClassMap建構子中完成的,或者在您放置其餘映射程式碼的任何地方完成:

Cache.ReadWrite().Region("Configuration");

必須對每個要記憶體的實體執行此操作。作為慣例,可能可以在一個地方設置,但是您幾乎錯過了使用區域的能力(並且在大多數係統中,您不希望記憶體事務數據和配置數據一樣多)。

就是這樣。確實不難做到,但要找到一個好的、完整的例子卻出奇地難,尤其是對於 FNH。


最後一點:這樣做的自然結果是,**當與查詢記憶體一起使用時,它會使急切的連接/獲取策略變得非常不可預測。**顯然,如果 NHibernate 發現查詢被記憶體,它不會首先檢查是否所有或什至任何實際實體都被記憶體。它幾乎只是假設它們是,並嘗試單獨載入每個。

這就是 SELECT N+1 災難的原因;如果 NH 注意到實體不在二級記憶體中,並且只是按照所寫的方式正常執行查詢,並使用 fetches 和 futures 等等,這不會有什麼大不了的。但它不會那樣做;相反,它嘗試一次載入每個實體及其關係、子關係和子子關係,依此類推。

因此,使用查詢記憶體幾乎沒有意義,除非您為整個圖中的**所有實體顯式啟用記憶體,即使那樣,您也需要非常小心(通過過期、依賴關係等) ) 記憶體查詢不會比它們應該檢索的實體更持久,否則您最終只會使性能變得更糟。

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