2011年12月18日 星期日

使用Lazy<T> … 在非ORM專案(Oracle)的應用


現在很多專案都已經使用Linq to SQL 或是 ADO.NET Entity Framework或者是其他的ORM Framework來做為專案中物件與資料存取對應的Solution,

就單純的以.NET Framework所推出的Linq to SQL 與ADO.NET Entity Framework來說,在資料庫中將各個Table的關連性給定義好之後,

到了程式設計階段時,使用工具產生Linq to SQL或是EF的定義模型時就會一併定義對應物件的關聯性,

這樣一來有關連的物件就可以取得其關連物件的物件集合,而且這個關連物件集合讀取就是以延遲載入的方式來使用。



ADO.NET Entity Framework的物件關連

Order物件與OrderDetails物件是有關連的,Order物件會有OrderDetails的物件集合,

當程式中去取得一個Order資料時,也會連帶的帶入OrderDetails物件集合,

但並非Order物件建立後就將OrderDetails一併從資料庫給讀出來,而會以延遲載入的方式,在程式中實際使用到才會從資料庫讀取出來,

這樣的觀念在使用Linq to SQL或是EF時是相當基礎與必要的觀念,但也就是因為這樣,就會有很多人覺得這是理所當然的,

image

尤其是一些專案經驗不是很多但是馬上就使用ORM的程式人員就會有這樣認知上的誤解,

很多專案於實務開發時並非可以完全的讓開發者去使用所謂新的技術或是使用ORM技術,而是還必須使用基礎的ADO.NET技術,

有這樣的考量,可能是客戶的系統限制,也可能是客戶有未來維護上的考量(用了ORM技術開發,但是後續的人卻不認識它)。

 

不使用ORM架構的專案開發

我目前所遇到的專案就是這樣的情況,客戶可以允許開發人員使用最新的 .NET 4.0 Framework,但是卻無法接受EF或是其他的ORM技術(因為資料庫是Oracle,所以有考慮用NHibernate),

客戶指定資料存取的部分要使用基礎的ADO.NET,資料庫無法使用Stored Procedure或是View,所以就是必須在程式中去下SQL指令,

坦白說,這對已經很久不曾在專案的程式中去寫SQL Command的我來說,這真的是一種觀念與開發習慣的衝擊,

因為依照客戶的限制下,我無法使用既有ORM技術,而我一定要使用物件導向來開發,

所以一開始的關連物件集合資料的取得就會另外使用Method的方式來操作,

image

 

其實遇到有關連的物件集合時,這種情況下我都習慣會在Order這樣的主物件去新增一個OrderDetails的Property,

用來裝載關連物件集合,這樣的設計就會符合物件關連的情境,

image

 

而原本的程式就可以修改成下面的樣子,

image

 

在程式執行的時候就會把Order關聯到的OrderDetails資料給載入,

image

 

但是這樣的方式並不是相當的直接,因為還必須在物件建立instance之後再另外去執行另外的方法來取得OrderDetails資料,

其實可以將取得資料的方式在Order類別的OrderDetails Property中,

image

 

將取得OrderDetails資料的動作移到Order物件中,當建立一個Order類別的instance時就會一併去將OrderDetails資料給取回,

image

 

但是這樣的作法看起來相當直接也很符合物件關連的情境,但是這樣的作法卻有個相當大的問題存在,

那就是當Order物件建立instance時就會一併把OrderDetail資料從資料庫中給取出來,

如果每一個類別中有建立很多這樣的關連物件的資料集合Property時,就會發生效能的問題,

因為每個物件建立instance時就把關連資料給取回,而關連的物件資料也會去抓它的關聯資料,這樣一直遞迴下去,

當資料少的時候是不會察覺有何效能上的影響,但是當資料越來越多時就會察覺這樣的效能問題,

每取一筆資料時就會讓關連資料的讀取動作一直執行下去,這真的會沒完沒了,

而在我以前的專案開發中就實際遇到這樣的問題,而我也一直無法有一個有效解決的方式。

 

物件建立instance時還不需要馬上就把關連資料從資料庫給取出來,而是當我需要去讀取關連的資料集合時才去資料庫給取出來,

這樣的方式就是延遲載入,在Linq to SQL或是EF的Solution就可以使用這樣的做法,但是非ORM的專案上就沒有比較好的解決方式,

我當時所使用的 .NET Framework 版本是 3.5,一直到了.NET 4.0時才有 Lazy<T> 這個類別的出現。

 

Lazy(Of T) 類別

http://msdn.microsoft.com/zh-tw/library/dd642331.aspx

提供延遲初始設定的支援。

第一次存取 Lazy(Of T).Value 屬性時,會發生延遲初始化。

使用 Lazy(Of T) 的執行個體來延遲建立大型或耗用大量資源的物件或耗用大量資源的任務的執行,特別是當這類的建立或執行可能不會在程式存留期 (Lifetime) 期間發生時。

 

Lazy(Of T) 建構函式 (Func(Of T))

http://msdn.microsoft.com/zh-tw/library/dd642329.aspx

初始化 Lazy(Of T) 類別的新執行個體。 當延遲初始設定發生時,會使用指定的初始設定函式。

 

 

使用Lazy<T>實作延遲載入

以下是Order類別並使用Lazy<T>的修改後的程式內容:

//前略...
/// <summary>
/// Initializes a new instance of the <see cref="Order"/> class.
/// </summary>
public Order()
{
  //建構式
  this._LazyOrderDetails = new Lazy<List<OrderDetail>>(this.LoadOrderDetails);
}
private Lazy<List<OrderDetail>> _LazyOrderDetails;
private List<OrderDetail> _OrderDetails = new List<OrderDetail>();
public List<OrderDetail> OrderDetails
{
  get
  {
    this._OrderDetails = this._LazyOrderDetails.Value;
    return this._OrderDetails;
  }
  set
  {
    _OrderDetails = value;
  }
}
/// <summary>
/// Loads the order details.
/// </summary>
/// <returns></returns>
private List<OrderDetail> LoadOrderDetails()
{
  OrderDetailService orderDetailService = new OrderDetailService();
  return orderDetailService.FindsByOrder(this.OrderID);
}

在Order類別中新增一個私有的Lazy<List<OrderDetail>欄位:_LazyOrderDetails

而在Order建構式中去對於這個Lazy<List<OrderDetail>欄位在初始化時去指定一個初始設定函式,

這個初始函式就是以Func方式去指定,指定一個當讀取延遲載入資料時所要載入的方法:LoadOrderDetails()

而原本Order類別中裝載關連的OrderDetail集合資料的Property:List<OrderDetail> OrderDetails

將get區段中原來直接去資料庫取值的程式用 _LazyOrderDetails.Value 來取代。

 

直接來看程式的執行,首先來看Order List的程式,這個程式中並不需要去讀取OrderDetail的關聯資料集合,

下面的圖可以清楚看出,

image

_LazyOrderDetails的IsValueCreated為false,IsValueCreated為表示是否建立此Lazy<T>的值,

所以當Order物件還沒有要使用其OrderDetail關連資料時並不會在物件建立instance時就去把資料給讀取出來,

而當需要使用到OrderDetails關連資料時呢?

 

一開始建立Order類別的instance時,還沒有去載入OrderDetails關連資料,

image

而當程式有使用到OrderDetails資料時,就會把關連資料從資料庫中讀取出來,

image

 

使用Lazy<T>類別就可以在非使用ORM的專案中去實作延遲載入的方法,

並且可以不用擔心類別中建立關連資料屬性時會去拖垮執行的效能,

使得非使用ORM技術的專案於物件導向的開發上更可以符合關連的情境以及關連資料的取得。

 

 

參考資料:

Lazy(Of T) 類別 - http://msdn.microsoft.com/zh-tw/library/dd642331.aspx

Lazy Initialization 延遲初始設定 - http://msdn.microsoft.com/zh-tw/library/dd997286(VS.100).aspx

HOW TO:執行物件的延遲初始化 - http://msdn.microsoft.com/zh-tw/library/dd460709.aspx

Gunnar Peipman's ASP.NET blog - Using Lazy<T> and abstract wrapper class to lazy-load complex system parameters

 

以上

沒有留言:

張貼留言

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。