如何使 NHibernate 記憶體獲取子集合?
我有一個相當簡單的標準查詢來獲取子集合,如下所示:
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搜尋發現了許多古老的問題,例如:
- NH-195:子集合未儲存在二級記憶體中
- Syscache2 二級記憶體:Child coll. 請求的對象
- SysCache 和 SysCache2 之間的奇怪差異
- NHibernate——提防不明智的記憶體策略(Ayende——當然他只是懶得提什麼不該做,而不是如何修復它……)
但是,這些似乎都是很久以前修復的,無論我使用哪個提供程序,我都會遇到同樣的不良行為。
我已經閱讀了關於 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 等等,這不會有什麼大不了的。但它不會那樣做;相反,它嘗試一次載入每個實體及其關係、子關係和子子關係,依此類推。
因此,使用查詢記憶體幾乎沒有意義,除非您為整個圖中的**所有實體顯式啟用記憶體,即使那樣,您也需要非常小心(通過過期、依賴關係等) ) 記憶體查詢不會比它們應該檢索的實體更持久,否則您最終只會使性能變得更糟。