翻譯的初衷以及為什么選擇《Entity Framework 6 Recipes》來學習,請看本系列開篇
第五章 加載實體和導航屬性
實體框架提供了非常棒的建模環境,它允許開發人員可視化地使用映射到數據庫中的表、視圖、存儲過程以及關系中的實體類型。本節將向你展示如何控制查詢操作中的關聯實體的加載。
實體框架的默認行為是只加載應用程序直接需要的實體。通常情況下,這正是你需要的。如果實體框架通過一個或多個關聯積極地加載關聯實體,最終,你很有可能得到超過你需求的實體。這不但增加了內存占用,而且還影響了應用程序的性能。
在實體框架中,當加載關聯實體時,你能控制並優化數據庫查詢執行的次數。如果在加載關聯對象時精心管理的話,能提供應用程序的性能,以及對數據有更多的控制。
在本章,我們將演示加載關聯數據的各種有效選項,並講述他們的優缺點。我們會特別地討論實體框架的默認行為 Lazy Loading(延遲加載)以及它的真正含義。然后,我們將演示在一個單獨查詢中,部分或者全部加載關聯實體的選項。這種類型的加載,叫做Eager Loading(預先加載),它既被用來減少數據交互,也被用來控制加載哪個關聯實體。
有的時候,你想延緩加載某一關聯實體,因為加載它的成本太高或者不經常使用。對於這種情況,我們將介紹另一種加載關聯實體的方法,它叫做Explicit Loading(顯示加載)。同時演示使用Load()方法來精確控制何時加載一個或多個關聯實體的多種場景。
最后,我們將簡要地看看異步操作。
5-1 延遲加載關聯實體
問題
你想加載一個實體,並在應用程序需要時才加載關聯實體。
解決方案
假設你有如圖5-1所示的模型。
圖5-1 包含Customer和與它相關聯信息的實體
在模型中,有一個Customer實體,一個與它關聯的CustomerType和多個與它關聯的CustomerEamil。它與CustomerType的關系是一對多關系,這是一個實體引用(譯注:Customer中的導航屬性CustomerType)。
Customer與CustomerEmail也是一對多關系,只是這時CustomerEmail在多的這一邊。這是一個實體集合(譯注:Customer中的導航屬性CustomerEmails)。
當把它們三個實體放在一起時,就得到一個叫做對象圖(object graph)的結構。一個對象圖包含單個且與之關聯的實體,它們構成一個整體的邏輯單元。特別地,一個對象圖是一個給定實體和與之關聯實體在某一特定時刻上的視圖。例如,在我們的應用操作中有一個ID為5,Name為“John Smith“的customer,它有一個類型為“perferred"的CustomerType,和一個包含10個CustomerEmails的集合。
代碼清單5-1.演示了實體框架加載關聯實體的默認行為,Lazy Loading(延遲加載)。
代碼清單5-1. 延遲加載Customer的關聯實體CustomerType和CustomerEmail的實例
1 using (var context = new EFRecipesEntities()) 2 { 3 var customers = context.Customers; 4 Console.WriteLine("Customers"); 5 Console.WriteLine("========="); 6 7 // 只需要Customer實體的信息 8 foreach (var customer in customers) 9 { 10 Console.WriteLine("Customer name is {0}", customer.Name); 11 } 12 13 14 //現在,需要使用到關聯實體CustomerType和CustomerEamil的信息, 15 //實體框架為每個實體對象產生一個單獨的查詢來獲取關聯實體的信息。 16 foreach (var customer in customers) 17 { 18 Console.WriteLine("{0} is a {1}, email address(es)", customer.Name, 19 customer.CustomerType.Description); 20 foreach (var email in customer.CustomerEmails) 21 { 22 Console.WriteLine("\t{0}", email.Email); 23 } 24 } 25 26 // Extra credit: 27 //如果你打開Sql Server Profiler,下面的查詢將不會重新去數據庫查詢,而是返回之前查詢的內存數據
//注:原書中的注釋有些模糊,本行是我(付燦)增加的說明,這里還要去查詢Customer表一次,但是它的關聯屬性,不會再去查詢數據庫了,而是直接從內存返回之前查詢出來的對象 28 foreach (var customer in customers) 29 { 30 Console.WriteLine("{0} is a {1}, email address(es)", customer.Name, 31 customer.CustomerType.Description); 32 foreach (var email in customer.CustomerEmails) 33 { 34 Console.WriteLine("\t{0}", email.Email); 35 } 36 } 37 }
代碼清單5-1的輸出如下:
Customers ========= Customer name is Joan Smith Customer name is Bill Meyers Joan Smith is a Web Customer, email address(es) jsmith@gmail.com joan@smith.com Bill Meyers is a Retail Customer, email address(es) bmeyers@gmail.com Joan Smith is a Web Customer, email address(es) jsmith@gmail.com joan@smith.com Bill Meyers is a Retail Customer, email address(es) bmeyers@gmail.com
原理
默認情況下,實體框架只加載確定需要使用的實體。這就是所謂的延遲加載,這是需要記住的一條很重要的准則。相反,加載父類和所有關聯的實體,這叫做Eager Loading(預先加載),它會可能會加載一個大的對象圖,這遠遠超出了你的需求。且不說獲取對象圖的開銷,封送,實例化。
在示例中,我們最先給出一個查詢,它查詢出所有的customers。有趣的是,這個查詢也不是立即執行的,它是在第一個foreach語句開始枚舉Customer實體時才被執行。這個延遲加載的行為是LINQ帶來的。
在第一個foreach中,我們只需要Custome表中的數據,不需要CustomerType和CustomerEmail表中的數據。在這種情況下,實體框架只查詢Customer表,不去查詢與 Customer表關聯的CustomerType和CustomerEmail表。
然后,在第二個foreach中,我們顯式引用了CustomerType實體中的Description屬性和CustomerEmail實體中的Email屬性。在實體框架中,直接訪問這些屬性,會為每個關聯的表生成一個查詢。這對理解實體框架在關聯表第一次被訪問時單獨生成查詢很重要。一旦通過關聯實體調用了查詢,實體框架會將該屬性標記為已加載(loaded),並在之后對該屬性的訪問中直接從內存返回數據,不再一次又一次地查詢數據庫。我們在這個示例中,一共為子數據(譯注:關聯實體數據)生成了4個單獨的查詢:
1、針對Joan Smith的CusotrmType和CustomerEmail的兩條Select語句;
2、針對Bill Meyers的CusotrmType和CustomerEmail的兩條Select語句;
當用戶在應用中按他們的需求瀏覽不同的數據進,這些針對每個子表的查詢工作得很好,它們能提高應用的響應時間。因為使用一系列按需獲取數據的簡短的查詢。相反,如果使用預先加載大量數據的方式,可能導致視圖加載遲緩。
然后,在你需要大量使用關聯表數據時,這個方法不是你看到的這么有效。在這種情況下,Eager Loading(預先加載)可能是一種更好的選擇,它在一個單獨的查詢中,從父表和與之關聯的表中獲取所有的數據。
最后一部分代碼塊,名為‘Extra Credit‘,演示了子屬性如果已經加載,實體框架會從內存中返回數據,而不是重新查詢數據庫。打開SQL Server Profiler工具,運行該示例,你會發現,在'Extra Credit'部分,當子屬性被引用時,代碼塊沒有生成SQL語句。
注意 SQL Server Profiler是一個檢查(監視)SQL Server中SQL語句非常棒的工具,它是免費的,包含在SQL Server Devoloper Edition和更高級的版本中:http://technet.microsoft.com/en-us/library/ms181091.aspx
實體框架交流QQ群: 458326058,歡迎有興趣的朋友加入一起交流
謝謝大家的持續關注,我的博客地址:http://www.cnblogs.com/VolcanoCloud/