《Entity Framework 6 Recipes》中文翻譯系列 (23) -----第五章 加載實體和導航屬性之預先加載與Find()方法

翻譯的初衷以及爲何選擇《Entity Framework 6 Recipes》來學習,請看本系列開篇html

5-2  預先加載關聯實體

問題web

  你想在一次數據交互中加載一個實體和與它相關聯實體。sql

解決方案數據庫

  假設你有如圖5-2所示的模型。app

圖5-2 包含Customer和與它相關聯信息的實體框架

  和5-1節同樣,在模型中,有一個Customer實體,一個與它關聯的CustomerType和多個與它關聯的CustomerEamil。它與CustomerType的關係是一對多關係,這是一個實體引用(譯註:Customer中的導航屬性CustomerType)。ide

  Customer與CustomerEmail也是一對多關係,只是這時CustomerEmail在多的這一邊。這是一個實體集合(譯註:Customer中的導航屬性CustomerEmails)。函數

  爲了在一次查詢中,獲取父對象customer和與它關聯的實體CustomerEamil和CustomrType的全部數據,咱們使用Include()方法。如代清單5-2所示。學習

代碼清單5-2. 預先加載與Customer相關聯的CustomerType和CustomerEmail實例ui

 1 using (var context = new EFRecipesEntities())
 2             {
 3                 var web = new CustomerType {Description = "Web Customer", CustomerTypeId = 1};
 4                 var retail = new CustomerType {Description = "Retail Customer", CustomerTypeId = 2};
 5                 var customer = new Customer {Name = "Joan Smith", CustomerType = web};
 6                 customer.CustomerEmails.Add(new CustomerEmail {Email = "jsmith@gmail.com"});
 7                 customer.CustomerEmails.Add(new CustomerEmail {Email = "joan@smith.com"});
 8                 context.Customers.Add(customer);
 9                 customer = new Customer {Name = "Bill Meyers", CustomerType = retail};
10                 customer.CustomerEmails.Add(new CustomerEmail {Email = "bmeyers@gmail.com"});
11                 context.Customers.Add(customer);
12                 context.SaveChanges();
13             }
14 
15             using (var context = new EFRecipesEntities())
16             {
17 
18                 //Include()方法,使用基於字符串類型的,與導航屬性相對應的查詢路徑
19                 var customers = context.Customers
20                                        .Include("CustomerType")
21                                        .Include("CustomerEmails");
22                 Console.WriteLine("Customers");
23                 Console.WriteLine("=========");
24                 foreach (var customer in customers)
25                 {
26                     Console.WriteLine("{0} is a {1}, email address(es)", customer.Name,
27                                       customer.CustomerType.Description);
28                     foreach (var email in customer.CustomerEmails)
29                     {
30                         Console.WriteLine("\t{0}", email.Email);
31                     }
32                 }
33             }
34 
35             using (var context = new EFRecipesEntities())
36             {
37                 //Include()方法,使用基於強類型的,與導航屬性相對應的查詢路徑
38                 var customerTypes = context.CustomerTypes
39                                            .Include(x => x.Customers
40                                                           .Select(y => y.CustomerEmails));
41 
42                 Console.WriteLine("\nCustomers by Type");
43                 Console.WriteLine("=================");
44                 foreach (var customerType in customerTypes)
45                 {
46                     Console.WriteLine("Customer type: {0}", customerType.Description);
47                     foreach (var customer in customerType.Customers)
48                     {
49                         Console.WriteLine("{0}", customer.Name);
50                         foreach (var email in customer.CustomerEmails)
51                         {
52                             Console.WriteLine("\t{0}", email.Email);
53                         }
54                     }
55                 }
56             }

 代碼清單5-2的輸出以下:

Customers
=========
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
Customers by Type
=================
Customer type: Web Customer
Joan Smith
jsmith@gmail.com
joan@smith.com
Customer type: Retail Customer
Bill Meyers
bmeyers@gmail.com

  

原理

  默認狀況下,實體框架只加載你指定的實體,這就是所謂的延遲加載。用戶在你的應用中會根據他的須要瀏覽不一樣的視圖,在這種狀況下延遲加載頗有效。

  與之相反的是,當即加載父實體和與之關聯的子實體(記住,對象圖是基於關聯的父實體和子實體,就像數據庫中基於外鍵的父表和子表)。它叫作Eager Loading(預先加載)。它在須要大量關聯數據時頗有效,由於它在一個單獨的查詢中獲取全部的數據(父實體和與之關聯的子實體)。

  在代碼清單5-2中,咱們兩次使用Include()方法(譯註:第一段代碼塊中),當即獲取對象圖。第一次,咱們加載一個包含Customer實體和實體引用CustmerType的對象圖。CustomerType在一對多關聯中的一這邊。第二次,咱們使用Include()方法(用相同的代碼串連在一塊兒)獲取一對多有關聯中多一邊的CustomerEmails。兩次經過fluent API方式將Include()方法連接在一塊兒,咱們從Customer的導航屬性獲取與其關聯的實體。注意,咱們在示例中使用字符串類型來表示導航屬性,使用"."字符來分隔(譯註:示例中沒有用到,好比這樣的的形式Include(「CustomerType.Customers」))。這種字符串形式的表示方式叫作關聯實體的查詢路徑(query path)

  在接下來的代碼塊中,咱們執行同樣的操做,但使用了強類型的查詢路徑。請注意咱們是如何使用lambda表達式來標識每個關聯實體的。強類型的用法給咱們帶來了智能提示、編譯時檢查和重構支持。

  請注意,代碼清單5-3中使用Include()方法產生的SQL查詢語句 。在結果集被實例化和返回以前,實體框架自動移除查詢中重複的數據。如圖5-3所示。

 

代碼清單5-3. 使用Include()方法產生的SQL查詢語句

 1 SELECT
 2 [Project1].[CustomerId] AS [CustomerId],
 3 [Project1].[Name] AS [Name],
 4 [Project1].[CustomerTypeId] AS [CustomerTypeId],
 5 [Project1].[CustomerTypeId1] AS [CustomerTypeId1],
 6 [Project1].[Description] AS [Description],
 7 [Project1].[C1] AS [C1],
 8 [Project1].[CustomerEmailId] AS [CustomerEmailId],
 9 [Project1].[CustomerId1] AS [CustomerId1],
10 [Project1].[Email] AS [Email]
11 FROM ( SELECT
12 [Extent1].[CustomerId] AS [CustomerId],
13 [Extent1].[Name] AS [Name],
14 [Extent1].[CustomerTypeId] AS [CustomerTypeId],
15 [Extent2].[CustomerTypeId] AS [CustomerTypeId1],
16 [Extent2].[Description] AS [Description],
17 [Extent3].[CustomerEmailId] AS [CustomerEmailId],
18 [Extent3].[CustomerId] AS [CustomerId1],
19 [Extent3].[Email] AS [Email],
20 CASE WHEN ([Extent3].[CustomerEmailId] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
21 FROM [Chapter5].[Customer] AS [Extent1]
22 INNER JOIN [Chapter5].[CustomerType] AS [Extent2] ON 
23 [Extent1].[CustomerTypeId] = [Extent2].[CustomerTypeId]
24 LEFT OUTER JOIN [Chapter5].[CustomerEmail] AS [Extent3] ON 
25 [Extent1].[CustomerId] = [Extent3].[CustomerId]
26 ) AS [Project1]
27 ORDER BY [Project1].[CustomerId] ASC, [Project1].[CustomerTypeId1] ASC, [Project1].[C1] ASC

 

圖5-3 經過使用Include()方法產生的冗餘數據

 

 

5-3  快速查詢一個單獨的實體

問題

  你想加載一個單獨的實體,可是,若是該實體已經加載到上下文中時,你不想再進行一次數據庫交互。同時,你想使用code-first 來管理數據訪問。

解決方案

  假設你有如圖5-4所示的模型。

圖5-4 包含一個Club實體類型的模型

 

  在這個模型中,咱們有一個實體類型Club,你能夠經過查詢獲取各類各樣的俱樂部(Clubs).

   在Visual Studio中添加一個名爲Recipe3的控制檯應用,並確保引用了實體框架6的庫,NuGet能夠很好的完成這個任務。在Reference目錄上右鍵,並選擇 Manage NeGet Packages(管理NeGet包),在Online頁,定位並安裝實體框架6的包。這樣操做後,NeGet將下載,安裝和配置實體框架6的庫到你的項目中。

  建立一個名爲Club類,複製代碼清單5-4中的屬性到這個類中,建立club實體。  (譯註:本書是多位做者寫的,描述的風格確定有所不一樣)

代碼清單5-4. Club 實體類

1   public class Club
2     {
3         public int ClubId { get; set; }
4         public string Name { get; set; }
5         public string City { get; set; }
6     }

  

   接下來,建立一個名爲Recipe3Context的類,並將代碼清單5-5中的代碼添加到其中,並確保其派生到DbContext類。

 1  public class Recipe3Context : DbContext
 2     {
 3         public Recipe3Context()
 4             : base("Recipe3ConnectionString")
 5         {
 6             // 禁用實體框架的模型兼容性
 7             Database.SetInitializer<Recipe3Context>(null);
 8         }
 9 
10         protected override void OnModelCreating(DbModelBuilder modelBuilder)
11         {
12              modelBuilder.Entity<Club>().ToTable("Chapter5.Club");
13         }
14 
15         public DbSet<Club> Clubs { get; set; }
16     }

 

  接下來添加App.Config文件到項目中,並使用代碼清單5-6中的代碼添加到文件的ConnectionStrings小節下。

<connectionStrings>
<add name="Recipe3ConnectionString"
connectionString="Data Source=.;
Initial Catalog=EFRecipes;
Integrated Security=True;
MultipleActiveResultSets=True"
providerName="System.Data.SqlClient" />
</connectionStrings>

  

  若是咱們正使用一個關鍵詞來搜索實體,通常是這樣操做過程,憑藉Find()方法,在從數據庫中獲取以前,先在內存中查找。記住,實體框架的默認行爲,當你給出一個獲取數據的操做時,它會去查詢數據庫,即便數據已經被加載到上下文中

  方法Find()是DbSet類中的成員函數,它是咱們用來註冊實體到上下文對象中的類。代碼清單5-7將對此進行演示。

代碼清單5-7. 憑藉實體框架中的Find()方法,避免獲取已經加載到上下文對象中的數據。

 1  int starCityId;
 2             int desertSunId;
 3             int palmTreeId;
 4 
 5             using (var context = new Recipe3Context())
 6             {
 7                 var starCity = new Club {Name = "Star City Chess Club", City = "New York"};
 8                 var desertSun = new Club {Name = "Desert Sun Chess Club", City = "Phoenix"};
 9                 var palmTree = new Club {Name = "Palm Tree Chess Club", City = "San Diego"};
10 
11                 context.Clubs.Add(starCity);
12                 context.Clubs.Add(desertSun);
13                 context.Clubs.Add(palmTree);
14                 context.SaveChanges();
15 
16                 // SaveChanges()返回每一個最新建立的Club Id
17                 starCityId = starCity.ClubId;
18                 desertSunId = desertSun.ClubId;
19                 palmTreeId = palmTree.ClubId;
20             }
21 
22             using (var context = new Recipe3Context())
23             {
24                 var starCity = context.Clubs.SingleOrDefault(x => x.ClubId == starCityId);
            starCity = context.Clubs.SingleOrDefault(x => x.ClubId == starCityId);
25 starCity = context.Clubs.Find(starCityId); 26 var desertSun = context.Clubs.Find(desertSunId); 27 var palmTree = context.Clubs.AsNoTracking().SingleOrDefault(x => x.ClubId == palmTreeId); 28 palmTree = context.Clubs.Find(palmTreeId); 29 var lonesomePintId = -999; 30 context.Clubs.Add(new Club {City = "Portland", Name = "Lonesome Pine", ClubId = lonesomePintId,}); 31 var lonesomePine = context.Clubs.Find(lonesomePintId); 32 var nonexistentClub = context.Clubs.Find(10001); 33 } 34 35 Console.WriteLine("Please run this application using SQL Server Profiler..."); 36 Console.ReadLine();

 

原理

  當使用上下文對象查詢時,即便數據已經加載到上下文中,仍會產生一次獲取數據的數據庫交互。當一次查詢完成時,不存在上下文中的實體對象將被添加到上下文中,並被跟蹤。在默認狀況下,若是實體對象已經在上下文中,實體框架不會使用數據庫中較新的值重寫它

  而後, DbSet對象,它包裝着咱們的實體對象,公佈了一個Find()方法。特別地,Find()方法指望獲得一個被查詢對象的主鍵(ID)參數。Find()方法很是有效率,由於它會先爲目標對象查詢上下文。若是對象不存在,它會自動去查詢底層的數據存儲。若是仍然沒有找到,Find()方法將返回NULL給調用者。另外,Find()方法將返回已添加到上下文中(狀態爲"Added"),但尚未保存到數據庫中的對象。Find()方法對三種建模方式均有效:Database First,Model First,Code First。

  在示例中,咱們添加三個clubs實體到Club實體集合。請注意,在調用SaveChanges()後,咱們是如何引用新建立的Club實體的ID的。當SaveChages()操做完成後,上下文會當即返回新建立對象的ID.

  接下來,咱們從DbContext中查詢實體,並返回StarCity Club 實體。注意,咱們是如何憑藉LINQ擴展方法SingleOrDefault(),返回一個對象的,若是在底層數據庫中不存在要查找的對象,它返回NULL。當發現多個符合給定條件的對象時,SingleOrDefault()方法將拋出一個異常。SingleOrDefault()在經過主鍵查找對象時,是一個很是好的方法。若是存在多個對象且你但願返回第一個時,能夠考慮使用FirstOrDefault()方法

  若是你運行SQL Profiler Tool(在SQL Server Developer Edition版本或更高版本中,SQL Express版本不包含),檢查底層數據庫的活動,你會看見如圖5-5所示的SQL查詢語句產生。

 圖5-5 返回 Star City Club的SQL的查詢語句

 

   請注意圖5-5,爲什麼在上下文對象中查詢Clubs,老是會產生一個針對底層數據庫的SQL查詢語句。這裏咱們獲取ID爲80的Club,將數據實例化到Club實體對象,並存放在上下文對象中。有趣的是,爲何LINQ擴展方法SingleOrDefault()老是產生一個Select Top 2 的SQL查詢。 Select Top 2 這條SQL查詢確保只有一行數據被返回。 若是多於一條數據返回, 實體框架將拋出一個異常,由於 SingleOrDefault()方法保證只返回一個單獨的結果。

  下一行代碼(譯註:指的是 starCity = context.Clubs.SingleOrDefault(x => x.ClubId == starCityId);),從新查詢數據庫獲取相同的對象,Star City Club。請注意,雖然對象已經存在上下文中,但實體框架DbContext的默認行爲,仍會從新查詢數據庫獲取記錄。在Profiler中,咱們看相同的SQL語句被產生。不只如此,由於Star City實體已經加載到上下文中,DbContext不會使用數據庫中的新值來替換當前的值,如圖5-6所示。

圖5-6 返回Star City Club的SQL語句

  下一行代碼,咱們再一次查找Star City Club。而後,此次咱們使用的是Find()方法,它是在DbSet類中公佈的。由於Clubs是一個DbSet類,所以,咱們只是在它身上簡單地調用Find()方法,並把要查找對象的主鍵做爲參數傳遞線它。在咱們示例中,主鍵的值爲80。

  Find()方法首先在上下文對象中查找Star City Club,找到對象後,它返回該對象的引用。關鍵點是,Find()方法只有在上下文中沒有找須要的對象時,纔去數據庫中查詢。請注意,圖5-7中爲何沒有產生SQL語句。

圖5-7 Find()在上下文中找到了對象,沒有產生任何針對數據庫查詢語句

 

  接下來,咱們再次使用Find()方法去獲取實體對象Desert Sun Club。方法Find()沒有在上下文中找到該對象,它將查詢數據庫並返回信息。圖5-8是它查詢該對象產生的SQL語句。

圖5-8 返回Desert Sun Club對象產生的SQL語句

 

  在下一個查詢中,咱們獲取實體對象Palm Tree Club的信息,可是咱們此次使用LINQ查詢。 注意AsNotracking()從句,它被添加到Clubs後面。NoTracking 選項將禁用指定對象的對象跟蹤。沒有了對象跟蹤,實體框架將不在跟蹤Palm Tree Club對象的改變。也不會將對象加載到上下文中

  隨後,當咱們查詢並獲取Palm Tree Club實體對象時,Find()方法將產生一個SQL查詢語句並從數據庫從獲取實體。如圖5-9所示。由於咱們使用AsNoTracking()從句指示實體框架不要在上下文中跟蹤對象,因此,數據庫交互就成了必須的了。記住,Find()方法須要對象跟蹤,以免數據庫調用 。

圖5-9 返回Desert Sun Club實體產生的SQL查詢語句

  

   接下來,咱們添加一個新的Club實體到上下文中。咱們實例化一個Club實體類,並填充必要的數據。爲Id分配一個臨時的值-999。記住,咱們不須要調用SaveChage()來提交新的Club對象,Lonesome Pine Club,到數據庫。有趣的是,咱們使用Find()方法並給它傳遞參數-999,實體框架從上下文中返回最新建立的 Lonesome Pine Club實體對象。你能夠從圖5-10中看到,此次調用Find()方法沒有產生數據庫活動。注意,Find()方法會返回一個最近添加到上下文中的實例,即便它尚未被保存到數據庫中

圖5-10 Find()方法在上下文中定位一個剛建立,但沒有保存的對象並返回,這個過程不生成sql查詢語句

 

   最後,咱們給Find()方法傳遞一個數據庫中不存在的Id做爲參數。這個Id的值爲10001.如圖5-11所示,Find()方法生成SQL查詢並試圖在數據庫中返回Id爲10001的記錄。跟LINQ擴展方法SingleOrDefault()同樣,若是沒有找到指定的記錄,會向調用方返回NULL。

圖5-11 Find()方法生成一個SQL查詢,若是數據庫中不存在要查找的記錄便返回null

 

 

實體框架交流QQ羣:  458326058,歡迎有興趣的朋友加入一塊兒交流

謝謝你們的持續關注,個人博客地址:http://www.cnblogs.com/VolcanoCloud/

相關文章
相關標籤/搜索