NHibernate之(12):初探延遲載入機制
本節內容
- 引入
- 延遲載入
- 例項分析
- 1.一對多關係例項
- 2.多對多關係例項
- 結語
返回文章列表
引入
通過前面文章的分析,我們知道了如何使用NHibernate,比如CRUD操作、事務、一對多、多對多對映等問題,這篇我們初步探索NHibernate中的載入機制。
在討論之前,我們看看我們使用的資料模型,回顧一下第二篇建立的資料模型。
Customer與Orders是一對多關係,Order與Product是多對多關係。這一篇還是使用這個模型,有關具體配置和對映參考本系列的文章。
延遲載入(Lazy Loading)
延遲載入按我現在的理解應該叫“視需要載入(load-on-demand)”,“(delayed loading)”,“剛好及時載入(just-in-time loading)”在合適不過了。這裡按字面理解延遲彷彿變成了“延遲,延長,拖延時間”的意思。
NHibernate從1.2版本就預設支援了延遲載入。其實延遲載入的執行方式是使用GoF23中的代理模式,我們用一張圖片來大致展示延遲載入機制。
例項分析
1.一對多關係例項
在一對多關係例項中,我們使用Customer物件與Order物件為例,在資料訪問層中編寫兩個方法用於在測試時呼叫,分別是:
資料訪問層中方法一:載入Customer物件
public Customer LazyLoad(int customerId) { return _session.Get<Customer>(customerId); }
資料訪問層中方法二:載入Customer物件並使用Using強制清理關閉Session
public Customer LazyLoadUsingSession(int customerId) { using (ISession _session = new SessionManager().GetSession()) { return _session.Get<Customer>(customerId); } }
1.預設延遲載入
呼叫資料訪問層中的LazyLoad方法載入一個Customer物件,NHibernate的預設延遲載入Customer關聯的Order物件。利用NHibernate提供有用類(NHibernateUtil)測試被關聯的Customer物件集合是否已初始化(也就是已載入)。
[Test] public void LazyLoadTest() { Customer customer = _relation.LazyLoad(1); Assert.IsFalse(NHibernateUtil.IsInitialized(customer.Orders)); }
測試成功,觀察NHibernate生成SQL語句為一條查詢Customer物件的語句。我們使用除錯發現,Orders物件集合的屬性值為:{Iesi.Collections.Generic.HashedSet`1[DomainModel.Entities.Order]},並可以同時看到Order物件集合中的項。截圖如下:
2.延遲載入並關閉Session
同第一個測試相同,這個測試呼叫使用Using強制資源清理Session載入Customer物件的方法。
[Test] public void LazyLoadUsingSessionTest() { Customer customer = _relation.LazyLoadUsingSession(1); Assert.IsFalse(NHibernateUtil.IsInitialized(customer.Orders)); }
測試成功,其生成SQL語句和上面測試生成SQL語句相同。但是使用除錯發現,Orders物件集合的屬性值為:NHibernate.Collection.Generic.PersistentGenericSet<DomainModel.Entities.Order> ,如果你進一步想看看Order物件集合中的項,它丟擲了HibernateException異常:failed to lazily initialize a collection, no session or session was closed。截圖如下:
2.多對多關係例項
同理,在多對多關係例項中,我們以Order物件與Products物件為例,我們在資料訪問層中寫兩個方法用於測試:
方法1:載入Order物件
public DomainModel.Entities.Order LazyLoadOrderAggregate(int orderId) { return _session.Get<DomainModel.Entities.Order>(orderId); }
方法2:載入Customer物件並使用Using強制清理關閉Session
public DomainModel.Entities.Order LazyLoadOrderAggregateUsingSession(int orderId) { using (ISession _session = new SessionManager().GetSession()) { return _session.Get<DomainModel.Entities.Order>(orderId); } }
1.預設延遲載入
呼叫資料訪問層中的LazyLoadOrderAggregate方法載入一個Order物件,NHibernate的預設延遲載入Order關聯的Products物件集合(多對多關係),利用代理模式載入Customer物件集合(多對一關係)。利用NHibernate提供有用類(NHibernateUtil)測試被關聯的Products物件集合和Customer物件集合是否已初始化。
[Test] public void LazyLoadOrderAggregateTest() { Order order = _relation.LazyLoadOrderAggregate(2); Assert.IsFalse(NHibernateUtil.IsInitialized(order.Customer)); Assert.IsFalse(NHibernateUtil.IsInitialized(order.Products)); }
測試成功,NHibernate生成SQL語句如下:
SELECT order0_.OrderId as OrderId1_0_, order0_.Version as Version1_0_, order0_.OrderDate as OrderDate1_0_, order0_.Customer as Customer1_0_ FROM [Order] order0_ WHERE order0_.OrderId=@p0; @p0 = '2'
除錯看看效果截圖,可以清楚的觀察到Customer物件和Products物件集合的型別。
2.延遲載入並關閉Session
同第一個測試相同,這個測試呼叫使用Using強制資源清理Session載入Order物件的方法。
[Test] public void LazyLoadOrderAggregateUsingSessionTest() { Order order = _relation.LazyLoadOrderAggregateUsingSession(2); Assert.IsFalse(NHibernateUtil.IsInitialized(order.Customer)); Assert.IsFalse(NHibernateUtil.IsInitialized(order.Products)); }
測試成功,其生成SQL語句和上面測試生成SQL語句相同。但是使用除錯發現,Customer物件型別為:{CustomerProxy9dfb54eca50247f69bfedd92e1638ba5},進一步觀察Customer物件Firstname、Lastname等項引發了“NHibernate.LazyInitializationException”型別的異常。Products物件集合的屬性值為:{NHibernate.Collection.Generic.PersistentGenericBag<DomainModel.Entities.Product>},如果你進一步想看看Products物件集合中的項它同樣丟擲HibernateException異常:failed to lazily initialize a collection, no session or session was closed。下面擷取獲取Customer物件一部分圖,你想知道全部自己親自除錯一把:
3.延遲載入中LazyInitializationException異常
上面測試已經說明了這個問題:如果我想在Session清理關閉之後訪問Order物件中的某些項會得到一個異常,由於session關閉,NHibernate不能為我們延遲載入Order項,我們編寫一個測試方法驗證一下:
[Test] [ExpectedException(typeof(LazyInitializationException))] public void LazyLoadOrderAggregateUsingSessionOnFailTest() { Order order = _relation.LazyLoadOrderAggregateUsingSession(2); string name = order.Customer.Name.Fullname; }
上面的測試丟擲“Could not initialize proxy - no Session”預計的LazyInitializationException異常,表明測試成功,證明不能載入Order物件的Customer物件。
4.N+1選擇問題
我們在載入Order後訪問Product項,導致訪問Product每項就會產生一個選擇語句,我們用一個測試方法來模擬這種情況:
[Test] public void LazyLoadOrderAggregateSelectBehaviorTest() { Order order = _relation.LazyLoadOrderAggregate(2); float sum = 0.0F; foreach (var item in order.Products) { sum += item.Cost; } Assert.AreEqual(21.0F, sum); }
NHibernate生成SQL語句如下:
SELECT order0_.OrderId as OrderId1_0_, order0_.Version as Version1_0_, order0_.OrderDate as OrderDate1_0_, order0_.Customer as Customer1_0_ FROM [Order] order0_ WHERE order0_.OrderId=@p0; @p0 = '2' SELECT products0_.[Order] as Order1_1_, products0_.Product as Product1_, product1_.ProductId as ProductId3_0_, product1_.Version as Version3_0_, product1_.Name as Name3_0_, product1_.Cost as Cost3_0_ FROM OrderProduct products0_ left outer join Product product1_ on products0_.Product=product1_.ProductId WHERE products0_.[Order]=@p0; @p0 = '2'
這次我走運了,NHibernate自動生成最優化的查詢語句,一口氣載入了兩個Product物件。但是試想一下有一個集合物件有100項,而你僅僅需要訪問其中的一兩項。這樣載入所有項顯然是資源的浪費。
幸好,NHibernate為這些問題有一個方案,它就是立即載入。欲知事後如何,請聽下回分解!
結語
這篇我們初步認識了NHibernate中的載入機制,這篇從一對多關係、多對多關係角度分析了NHibernate預設載入行為——延遲載入,下篇繼續分析立即載入。希望對你有所幫助。
本系列連結:NHibernate之旅系列文章導航
下次繼續分享NHibernate!