《Entity Framework 6 Recipes》中文翻譯系列 (26) ------ 第五章 加載實體和導航屬性之延緩加載關聯實體和在別的LINQ查詢操做中使用Include()方法

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

5-7  在別的LINQ查詢操做中使用Include()方法

問題html

  你有一個LINQ查詢,使用了相似這樣的操做 group by,join,和where;你想使用Include()方法預先加載額外的實體。另外你想使用Code-First來管理數據訪問。java

解決方案數據庫

  假設你有如圖5-22所示的概念模型框架

 

圖5-22 一個簡單的包含Club和Event以及它們之間一對多關聯的模型ide

 

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

  建立一個名爲Club和Event的類,複製代碼清單5-41中的屬性到這個類中,建立實體Club和Event實體。ui

代碼清單5-14. Club、Event 實體類this

 1     public class Club
 2     {
 3         public Club()
 4         {
 5             Events = new HashSet<Event>();
 6         }
 7 
 8         public int ClubId { get; set; }
 9         public string Name { get; set; }
10         public string City { get; set; }
11 
12         public virtual ICollection<Event> Events { get; set; }
13     }
14 
15     public class Event
16     {
17         public int EventId { get; set; }
18         public string EventName { get; set; }
19         public DateTime EventDate { get; set; }
20         public int ClubId { get; set; }
21 
22         public virtual Club Club { get; set; }
23     }

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

複製代碼
 1  public class Recipe7Context : DbContext  2  {  3 public Recipe7Context()  4 : base("Recipe7ConnectionString")  5  {  6 // 禁用實體框架的模型兼容性  7 Database.SetInitializer<Recipe7Context>(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="Recipe7ConnectionString" connectionString="Data Source=.; Initial Catalog=EFRecipes; Integrated Security=True; MultipleActiveResultSets=True" providerName="System.Data.SqlClient" /> </connectionStrings>
複製代碼

  爲了與group by從句組合使用Include()方法,Include()方法必須放在針對父實體的過慮和分組操做以後。如代碼清單5-17所示。

代碼清單5-17 . 當父實體上應用過慮和分組時,Include()方法的正確位置。

 1 using (var context = new Recipe7Context())
 2             {
 3                 var club = new Club {Name = "Star City Chess Club", City = "New York"};
 4                 club.Events.Add(new Event
 5                     {
 6                         EventName = "Mid Cities Tournament",
 7                         EventDate = DateTime.Parse("1/09/2010"),
 8                         Club = club
 9                     });
10                 club.Events.Add(new Event
11                     {
12                         EventName = "State Finals Tournament",
13                         EventDate = DateTime.Parse("2/12/2010"),
14                         Club = club
15                     });
16                 club.Events.Add(new Event
17                     {
18                         EventName = "Winter Classic",
19                         EventDate = DateTime.Parse("12/18/2009"),
20                         Club = club
21                     });
22 
23                 context.Clubs.Add(club);
24 
25                 context.SaveChanges();
26             }
27 
28             using (var context = new Recipe7Context())
29             {
30                 var events = from ev in context.Events
31                              where ev.Club.City == "New York"
32                              group ev by ev.Club
33                              into g
34                              select g.FirstOrDefault(e1 => e1.EventDate == g.Min(evt => evt.EventDate));
35 
36                 var eventWithClub = events.Include("Club").First();
37 
38                 Console.WriteLine("The next New York club event is:");
39                 Console.WriteLine("\tEvent: {0}", eventWithClub.EventName);
40                 Console.WriteLine("\tDate: {0}", eventWithClub.EventDate.ToShortDateString());
41                 Console.WriteLine("\tClub: {0}", eventWithClub.Club.Name);
42             }
43 
44             Console.WriteLine("Press <enter> to continue...");
45             Console.ReadLine();

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

The next New York club event is:
    Event: Winter Classic
    Date: 12/18/2009
    Club: Star City Chess Club

原理

  咱們建立了一個俱樂部(Club)和三個Events(活動)實體對象。在查詢中,咱們獲取New York 的俱樂部中的全部活動。按俱樂部分組,並查找日期中最先的活動。注意,LINQ擴展方法FirstOrDefault(),巧妙地嵌入到了Select投影操做中。然而變量events只是一個表達式,它尚未在數據庫中執行任何操做。

  接下來,咱們憑藉Include()方法預先加載關聯實體Club對象的信息,咱們將第一個LINQ查詢變量,events,做爲第二個LINQ查詢的輸入。這是LINQ組合查詢的一個示例。將一個複雜的LINQ查詢轉換成一系列的短小查詢。前面的查詢變量是後面查詢的數據源。

  注意,咱們使用First()方法只是爲獲取第一個Event實例,這樣將返回一個Event類型,而不是Event對象的集合。實體框架6包含了一個新的名爲 IQueryableExtensions的接口,它公佈了Include()方法的原型,它能接受一個基於字符串或是強類型的查詢路徑做爲參數。IQueryableExtensions替換了EF4和EF5中的DbExtension類。

  不少開發人員以爲Include()方法很讓人迷惑,在一些狀況下,智能感知不能有效地提示(由於表達式類型)。在一些狀況下,它會在運行時悄悄地被忽略掉。特別地,除非編譯器沒法肯定其結果類型,不然不會給出警告或提示。不少問題都在運行時纔會暴露出來,這會使問題更難解決。這裏有一些使用Include()方法的準則:

    一、Include()方法是一個在IQueryable<T>上的擴展方法;

    二、Include()方法只能應用在最終的查詢結果集上,當它被在subquery(子查詢)、join(鏈接)或者嵌套從句中,當生成命令樹時,它將被忽略掉。在幕後,實體框架會把你的LINQ to Entites查詢轉換成一棵命令樹,而後數據庫提供者(database provider)將其處理並構建成一個用於數據庫的SQL查詢(譯註:這一點很重要,我在上面吃過虧,直到如今才弄明白)

    三、Include()方法只能應用 在實體類型的結果集上,若是表達式將結果投影到一個非實體類型的類型上,Include()方法將被忽略。

    四、在Include()方法和最外面的操做之間,不能改變結果集的類型。例如,一個group by從句,改變結果集的類型。

    五、用於Include()方法的查詢路徑表達式必須從最外層操做返回的類型中的導航屬性開始,查詢路徑不能從任意點開始。

  讓咱們看看,代碼清單5-17是如何運行這些規則的,這個查詢,將活動(events)按贊助的俱樂部分組,group by將結果類型從Event改變成一個分組結果集。第四條規則告訴咱們,須要在group by從句改變結果類型以後再調用Include()。咱們在結尾處調用Include()方法。 若是咱們過早應用Include()方法,像這樣 from ev in context.Events.Include,那麼,Include()方法將被悄悄地從命令樹上移除並不在起用。

 

 

5-8  延緩加載(Deferred Loading)相關實體

問題

  你有一個實體的實例,你想在一個單獨的查詢中延緩加載其中兩個或多個關聯實體。這裏尤爲重要的是,咱們如何使用Load()方法來避免查詢相同的對象兩次。另外你想使用Code-First來管理數據訪問。

 

解決方案

  假設你有如圖5-23所示的概念模型.

圖5-23 一個包含職員(employee),他的部門(department)和部門所在的公司(company)的模型

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

  接下來咱們建立三個實體對象:Company,Departmet和Employee,複製代碼清單5-18中的屬性到這三個類中。

代碼清單5-18. 實體類

 1 public class Company
 2     {
 3         public Company()
 4         {
 5             Departments = new HashSet<Department>();
 6         }
 7 
 8         public int CompanyId { get; set; }
 9         public string Name { get; set; }
10 
11         public virtual ICollection<Department> Departments { get; set; }
12     }
13 
14 
15  public class Department
16     {
17         public Department()
18         {
19             this.Employees = new HashSet<Employee>();
20         }
21 
22         public int DepartmentId { get; set; }
23         public string Name { get; set; }
24         public int CompanyId { get; set; }
25 
26         public virtual Company Company { get; set; }
27         public virtual ICollection<Employee> Employees { get; set; }
28     }
29 
30  public class Employee
31     {
32         public int EmployeeId { get; set; }
33         public string Name { get; set; }
34         public int DepartmentId { get; set; }
35 
36         public virtual Department Department { get; set; }
37     }

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

 1 public partial class Recipe8Context : DbContext
 2     {
 3         public Recipe8Context()
 4             : base("Recipe8ConnectionString")
 5         {
 6             // 禁用實體框架的模型兼容性
 7             Database.SetInitializer<Recipe8Context>(null);
 8         }
 9 
10         protected override void OnModelCreating(DbModelBuilder modelBuilder)
11         {
12             modelBuilder.Entity<Company>().ToTable("Chapter5.Company");
13             modelBuilder.Entity<Employee>().ToTable("Chapter5.Employee");
14             modelBuilder.Entity<Department>().ToTable("Chapter5.Department");
15         }
16 
17         public DbSet<Company> Companies { get; set; }
18         public DbSet<Department> Departments { get; set; }
19         public DbSet<Employee> Employees { get; set; }
20     }

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

複製代碼
<connectionStrings>
<add name="Recipe8ConnectionString" connectionString="Data Source=.; Initial Catalog=EFRecipes; Integrated Security=True; MultipleActiveResultSets=True" providerName="System.Data.SqlClient" /> </connectionStrings>
複製代碼

 

  在圖5-23所示的模型中,一個職員(Employee)被關聯到一個確切的部門(Department)。每一個部門被關聯到一個確切公司(Company)。

  給定一個Employee的實例,你想加載他的部門以及部門所在的公司。是什麼讓這個問題變得有點特別呢? 咱們已經有了一個Employee的實例,咱們想避免再一次到數據庫中獲取Emplyee對象的副本,這種狀況,咱們可使用Include()方法來獲取關聯實體Company和Department。也許,在真實的狀況中,Employee的獲取和實例化須要很是高的代價。

  咱們可使用Load()方法,兩次去加載關聯實體,一次加載Department實例,一次去加載Company實例。然而,這會產生兩次數據庫交互。爲了在一個查詢中加載關聯實體的實例,咱們可使用Include()方法和包含Department,Company查詢路徑,從新在Emlpoyee實體集上查詢。或者組合使用Reference()和Query()方法。代碼清單5-21演示了這些方法。

代碼清單5-21.將數據插入到模型並使用兩種稍有不一樣的方法加載關聯實體

 using (var context = new Recipe8Context())
            {
                var company = new Company {Name = "Acme Products"};
                var acc = new Department {Name = "Accounting", Company = company};
                var ship = new Department {Name = "Shipping", Company = company};
                var emp1 = new Employee {Name = "Jill Carpenter", Department = acc};
                var emp2 = new Employee {Name = "Steven Hill", Department = ship};
                context.Employees.Add(emp1);
                context.Employees.Add(emp2);
                context.SaveChanges();
            }

            // 第一種方法
            using (var context = new Recipe8Context())
            {
                // 假設咱們已經擁有一個employee
                var jill = context.Employees.First(o => o.Name == "Jill Carpenter");

                // 獲取Jill的部門和公司, 但咱們須要從新加載employee
                var results = context.Employees.Include("Department.Company")
                                     .First(o => o.EmployeeId == jill.EmployeeId);
                Console.WriteLine("{0} works in {1} for {2}", jill.Name, jill.Department.Name,
                                  jill.Department.Company.Name);
            }

            //更有效的方法, 不用再加載employee
            using (var context = new Recipe8Context())
            {
                // 假設咱們已經擁有一個employee
                var jill = context.Employees.Where(o => o.Name == "Jill Carpenter").First();


                //憑藉Entry、Reference,Query和Include方法獲取Department和Company數據,不用去查詢底層的Employee表
                context.Entry(jill).Reference(x => x.Department).Query().Include(y => y.Company).Load();

                Console.WriteLine("{0} works in {1} for {2}", jill.Name, jill.Department.Name,
                                  jill.Department.Company.Name);
            }

            Console.WriteLine("Press <enter> to continue...");
            Console.ReadLine();

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

Jill Carpenter works in Accounting for Acme Products
Jill Carpenter works in Accounting for Acme Products

原理

  若是咱們尚未Employee實體的實例,咱們能夠簡單地使用Include()方法和一個查詢路徑 Department.Company來實現 。這是咱們這前使用的方法。它的缺點是,它會獲取employee實體的全部列。有不少狀況下,這是一個昂貴的操做。由於咱們已經加載對象到上下文中,再一次去數據庫獲取全部的列並傳輸到上下文中,這是一個浪費!

  在第二個查詢中,咱們使用上下文對象DbContext公佈的Entry()方法訪問Employee對象並對其執行操做。而後咱們鏈式調用Reference()方法和DbReferenceEntity類的Query()方法,返回一個從數據庫中加載關聯對象Deparment的查詢。另外,咱們鏈式調用Include()方法來拉取關聯對象Company的信息。正如所指望的那樣,這個查詢獲取了Department和Company的數據,它沒有去獲取Employees的數據,由於這些數據已經存在於上下文對象中了。

  

 

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

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

相關文章
相關標籤/搜索