1. 程式人生 > >Entity Framework 4.1 之三 : 貪婪載入和延遲載入

Entity Framework 4.1 之三 : 貪婪載入和延遲載入

原文名稱:Entity Framework 4.1: Deep Fetch vs Lazy Load (3)

這篇文章將討論查詢結果的載入控制。

EF4.1 允許控制物件之間的關係,當我們進行查詢的時候,哪些關係的資料將會被載入到記憶體呢?所有相關的物件都需要嗎?在一些場合可能有意義,例如,當查詢的實體僅僅擁有一個相關的子實體,但是,多數情況下,你可能只需要載入部分資料,或者你喜歡的話,載入更多的資料。

預設情況下, EF4.1 僅僅載入查詢中涉及的實體,但是它支援兩種特性來幫助你控制載入:

  • 貪婪載入
  • 延遲載入

貪婪載入

對於下面的查詢

using (var context 
=new MyDomainContext())
{

var orders
= from o in context.Orders.Include("OrderDetails")
where o.CustomerName =="Mac"
select o;

這裡我指定載入某些訂單,就是客戶名為 Mac 的客戶的訂單,而且希望相關的訂單明細也一起載入。

你可以這樣檢視實際執行的 SQL 查詢

Console.WriteLine(orders.ToString());

實際的 SQL 如下所示:

SELECT[Project1
].[OrderID]AS[OrderID],
[Project1].[OrderTitle]AS[OrderTitle],
[Project1].[CustomerName]AS[CustomerName],
[Project1].[TransactionDate]AS[TransactionDate],
[Project1].[C1]AS[C1],
[Project1].[OrderDetailID]AS[OrderDetailID],
[Project1].[OrderID1]AS[OrderID1],
[Project1].[Cost]AS[Cost],
[Project1].[ItemName]AS
[ItemName]FROM ( SELECT[Extent1].[OrderID]AS[OrderID],
[Extent1].[OrderTitle]AS[OrderTitle],
[Extent1].[CustomerName]AS[CustomerName],
[Extent1].[TransactionDate]AS[TransactionDate],
[Extent2].[OrderDetailID]AS[OrderDetailID],
[Extent2].[OrderID]AS[OrderID1],
[Extent2].[Cost]AS[Cost],
[Extent2].[ItemName]AS[ItemName],
CASEWHEN ([Extent2].[OrderDetailID]ISNULL) THENCAST(NULLASint) ELS
E
1ENDAS[C1]FROM[dbo].[Orders]AS[Extent1]LEFTOUTERJOIN[dbo].[OrderDetails]AS[Extent2]ON[Extent1].[OrderID]=[Extent2].[OrderID]WHERE N'Mac'=[Extent1].[CustomerName]
)
AS[Project1]ORDERBY[Project1].[OrderID]ASC, [Project1].[C1]ASC

EF4.1 生成的 SQL 不是特別易讀,但是這個查詢你應該能夠看懂,訂單明細被一起載入了。

這帶來了一個關於貪婪載入的問題:查詢效率。如果你執行這樣的查詢來獲取訂單和訂單明細,也可以變成寫出等效的查詢語句,如果喜歡的話,可以更加聰明地寫出返回兩個查詢結果的查詢,一個是訂單,另外一個是訂單明細。這應該更加有效,因為你不需要為每一個訂單明細重複訂單的資訊。由於一些原因,EF 並不支援。記住這一點,因為這很容易使效能變差。

無論如何,你還可以在查詢中包含更多的子集。

var orders =from o in context.Orders.Include("OrderDetails").Include("Businesses") 
where o.CustomerName == "Mac"
select o;

延遲載入

另外一個特性就是延遲載入,預設情況下,延遲載入被支援,如果你希望禁用它,必須顯式宣告,最好的位置是在 DbContext 的構造器中。

public MyDomainContext() 
{
this.Configuration.LazyLoadingEnabled
= false;
}

這樣延遲載入就如你所願了。當查詢一個實體集的時候,相關的子實體也一併載入。

當 EF 訪問實體的子實體的時候是如何工作的呢?你的集合是 POCO 的集合,所以,在訪問的時候沒有事件發生,EF 通過從你定義的實體派生一個動態的物件,然後覆蓋你的子實體集合訪問屬性來實現。這就是為什麼需要標記你的子實體集合屬性為 virtual 的原因。

public class Order 
{
publicint OrderID { get; set; }
public string OrderTitle { get; set; }
public string CustomerName { get; set; }
publicDateTime TransactionDate { get; set; }
public virtual List<OrderDetail> OrderDetails { get; set; }
public virtual List<Business> Businesses { get; set; }
}

總結一下兩種載入方式的特點

貪婪載入:

  • 減少資料訪問的延遲,在一次資料庫的訪問中返回所有的資料。
  • 你需要知道你將作什麼,並且顯式宣告

延遲載入:

  • 非常寬容,因為只在需要的時候載入資料,不需要預先計劃
  • 可能因為資料訪問的延遲而降低效能,考慮到每訪問父實體的子實體時,就需要訪問資料庫。

現在,什麼時候我們應該使用哪種機制?我的建議是:除非需要迴圈中載入資料,我使用延遲載入。這樣的話,可能會造成2-3 次伺服器的查詢,但是仍然是可以接受的,特別是考慮到貪婪載入的效率問題。