程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> NHibernate之旅(12):初探延遲加載機制

NHibernate之旅(12):初探延遲加載機制

編輯:關於.NET

節內容

引入

延遲加載

實例分析

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默認加載行為——延遲加載,下篇繼續分析立即加載。希望對你有所幫助。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved