《Entity Framework 6 Recipes》中文翻譯系列 (15) -----第三章 查詢之與列表值比較和過濾關聯實體


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

3-8與列表值比較

問題

  你想查詢一個實體,條件是給定的列表中包含指定屬性的值。

解決方案

  假設你有如圖3-9所示的模型。

 

圖3-9 包含books和它的categoryes的模型

 

   你想查找給定目錄列表中的所有圖書。在代碼清單3-16中使用LINQ和Entity SQL來實現這上功能。

代理清單3-16. 使用LINQ和Entity SQL來查找給定目錄列表中的所有圖書

 1  using (var context = new EFRecipesEntities())
 2             {
 3                 // 刪除之前的測試數據
 4                 context.Database.ExecuteSqlCommand("delete from chapter3.book");
 5                 context.Database.ExecuteSqlCommand("delete from chapter3.category");
 6                 // 添加新的測試數據            
 7                 var cat1 = new Category {Name = "Programming"};
 8                 var cat2 = new Category {Name = "Databases"};
 9                 var cat3 = new Category {Name = "Operating Systems"};
10                 context.Books.Add(new Book {Title = "F# In Practice", Category = cat1});
11                 context.Books.Add(new Book {Title = "The Joy of SQL", Category = cat2});
12                 context.Books.Add(new Book
13                 {
14                     Title = "Windows 7: The Untold Story",
15                     Category = cat3
16                 });
17                 context.SaveChanges();
18             }
19 
20             using (var context = new EFRecipesEntities())
21             {
22                 Console.WriteLine("Books (using LINQ)");
23                 var cats = new List<string> {"Programming", "Databases"};
24                 var books = from b in context.Books
25                     where cats.Contains(b.Category.Name)
26                     select b;
27                 foreach (var book in books)
28                 {
29                     Console.WriteLine("'{0}' is in category: {1}", book.Title,
30                         book.Category.Name);
31                 }
32             }
33 
34             using (var context = new EFRecipesEntities())
35             {
36                 Console.WriteLine("\nBooks (using eSQL)");
37                 var esql = @"select value b from Books as b
38                  where b.Category.Name in {'Programming','Databases'}";
39                 var books = ((IObjectContextAdapter) context).ObjectContext.CreateQuery<Book>(esql);
40                 foreach (var book in books)
41                 {
42                     Console.WriteLine("'{0}' is in category: {1}", book.Title,
43                         book.Category.Name);
44                 }
45             }
46 
47             Console.WriteLine("\nPress <enter> to continue...");
48             Console.ReadLine();
49         }

代碼清單3-16的輸出如下:

Books (using LINQ)
'F# In Practice' is in category: Programming
'The Joy of SQL' is in category: Databases
Books (using ESQL)
'F# In Practice' is in category: Programming
'The Joy of SQL' is in category: Databases

 

原理

  在LINQ查詢中,我構造了一個簡單的目錄名列表,它使用LINQ查詢操作符Contains。細心的讀者可以注意了,我使用cats集合,並判斷它是否包含某一目錄名。實體框架將Containts從句轉換成SQL語句中的in從句。如代碼清單3-17所示:

代碼清單3-17.  代碼清單3-16中LINQ查詢表達式對應的SQL語句

1 SELECT
2 [Extent1].[BookId] AS [BookId],
3 [Extent1].[Title] AS [Title],
4 [Extent1].[CategoryId] AS [CategoryId]
5 FROM [chapter3].[Books] AS [Extent1]
6 LEFT OUTER JOIN [chapter3].[Category] AS [Extent2] ON [Extent1].[CategoryId] = [Extent2].[CategoryId]
7 WHERE [Extent2].[Name] IN (N'Programming',N'Databases')

  有趣的是,代碼清單3-17中的SQL語句,沒有為從句的項使用參數。 這不同於LINQ to SQL中產生的代碼,列表中的項會被參數化。超過SQL Server的參數限制的風險,會使代碼不能運行。

  如果我們需要查找出給定目錄列表中的所有書,列表目錄中包含未分類的目錄,我們只需在目錄列表中簡單地使用null值。產生的SQL語句,如代碼清單3-18所示。

代碼清單3-18.代碼清單3-16中LINQ查詢表達式對應的SQL語句,但是目錄列表中多了一上null值。

1 SELECT
2 [Extent1].[BookId] AS [BookId],
3 [Extent1].[Title] AS [Title],
4 [Extent1].[CategoryId] AS [CategoryId]
5 FROM [chapter3].[Books] AS [Extent1]
6 LEFT OUTER JOIN [chapter3].[Category] AS [Extent2] ON [Extent1].[CategoryId] = [Extent2].[CategoryId]
7 WHERE [Extent2].[Name] IN (N'Programming',N'Databases')
8 OR [Extent2].[Name] IS NULL

 

  相同地,我們包含了一個使用Entity SQL的查詢版本,它顯式地包含了一個SQL IN 從句。

 

3-9過濾關聯實體

問題

  你想獲取一部分,不是全部關聯實體。

解決方案

  假設你有如圖3-10所示的模型。

圖3-10 包含Worker和他們的Accidents的模型

 

  在模型中,一個Worker有零個或是多個accidents.每個事故按嚴重性分類,我們要獲取所有的工人,對於與之關聯的事故,只對嚴重事故感興趣,事故的嚴重性大於2.

  在示例中我們使用了Code-First,在代碼清單3-19中,創建了實體類Worker和Accidentd.

代碼清單3-19. 實體類

 1  public class Worker
 2     {
 3         public Worker()
 4         {
 5             Accidents = new HashSet<Accident>();
 6         }
 7 
 8         public int WorkerId { get; set; }
 9         public string Name { get; set; }
10 
11         public virtual ICollection<Accident> Accidents { get; set; }
12     }
13 
14 
15  public class Accident
16     {
17         public int AccidentId { get; set; }
18         public string Description { get; set; }
19         public int? Severity { get; set; }
20         public int WorkerId { get; set; }
21 
22         public virtual Worker Worker { get; set; }
23     }

  接下來,我們在代碼清單3-20中創建Code-First中使用的上下文對象。

代碼清單3-20. 上下文對象

 1 public class EFRecipesEntities : DbContext
 2     {
 3         public EFRecipesEntities()
 4             : base("ConnectionString") {}
 5 
 6         public DbSet<Accident> Accidents { get; set; }
 7         public DbSet<Worker> Workers { get; set; }
 8 
 9         protected override void OnModelCreating(DbModelBuilder modelBuilder)
10         {
11             modelBuilder.Entity<Accident>().ToTable("Chapter3.Accident");
12             modelBuilder.Entity<Worker>().ToTable("Chapter3.Worker");
13             base.OnModelCreating(modelBuilder);
14         }
15     }

  使用代碼清單3-21中的模式,獲取所有的worders,和嚴重事故。

代碼清單3-21 使用CreateSourceQuery和匿名類型獲取嚴重事故

 1 using (var context = new EFRecipesEntities())
 2             {
 3                 // 刪除之前的測試數據
 4                 context.Database.ExecuteSqlCommand("delete from chapter3.accident");
 5                 context.Database.ExecuteSqlCommand("delete from chapter3.worker");
 6                 // 添加新的測試數據
 7                 var worker1 = new Worker { Name = "John Kearney" };
 8                 var worker2 = new Worker { Name = "Nancy Roberts" };
 9                 var worker3 = new Worker { Name = "Karla Gibbons" };
10                 context.Accidents.Add(new Accident
11                 {
12                     Description = "Cuts and contusions",
13                     Severity = 3,
14                     Worker = worker1
15                 });
16                 context.Accidents.Add(new Accident
17                 {
18                     Description = "Broken foot",
19                     Severity = 4,
20                     Worker = worker1
21                 });
22                 context.Accidents.Add(new Accident
23                 {
24                     Description = "Fall, no injuries",
25                     Severity = 1,
26                     Worker = worker2
27                 });
28                 context.Accidents.Add(new Accident
29                 {
30                     Description = "Minor burn",
31                     Severity = 3,
32                     Worker = worker2
33                 });
34                 context.Accidents.Add(new Accident
35                 {
36                     Description = "Back strain",
37                     Severity = 2,
38                     Worker = worker3
39                 });
40                 context.SaveChanges();
41             }
42 
43             using (var context = new EFRecipesEntities())
44             {
45                 // 顯式禁用延遲加載
46                 context.Configuration.LazyLoadingEnabled = false;
47                 var query = from w in context.Workers
48                             select new
49                             {
50                                 Worker = w,
51                                 Accidents = w.Accidents.Where(a => a.Severity > 2)
52                             };
53                 query.ToList();
54                 var workers = query.Select(r => r.Worker);
55                 Console.WriteLine("Workers with serious accidents...");
56                 foreach (var worker in workers)
57                 {
58                     Console.WriteLine("{0} had the following accidents", worker.Name);
59                     if (worker.Accidents.Count == 0)
60                         Console.WriteLine("\t--None--");
61                     foreach (var accident in worker.Accidents)
62                     {
63                         Console.WriteLine("\t{0}, severity: {1}",
64                               accident.Description, accident.Severity.ToString());
65                     }
66                 }
67             }
68 
69             Console.WriteLine("\nPress <enter> to continue...");
70             Console.ReadLine();
71         }

下面是代碼清單3-21的輸出:

Workers with serious accidents...
John Kearney had the following accidents
Cuts and contusions, severity: 3
Broken foot, severity: 4
Nancy Roberts had the following accidents
Minor burn, severity: 3
Karla Gibbons had the following accidents
--None--

原理

  正如你在隨后的第五章中看到的那樣,我們需要立即加載一個關聯集合,我們經常使用Include()方法和一個查詢路徑。(Include()方法在單個查詢中返回父對象和所有的子對象)。然后,Include()方法不允許過濾關聯的子實體對象,在本節中,我們展示了,通過輕微的改變,讓你可以加載並過濾相關聯的子實體對象。

  在代碼塊中,我們創建了一些workers,並分配給它們相關的不同等級的accidents。不得不承認,分配事故給人,有點毛骨悚然!但,這只是為了得到一些可以讓我們繼續工作的數據。

  在隨后的查詢中,我們獲取所有的工人並將結果投射到匿名對象上,這個類型包含了一個worker和一個accidents集合。對於accidents,應該注意,我們是如何過濾集合,只獲取嚴重事故的。

  接下來的一行,非常重要!(譯注:也就是query.ToList();),我們通過調用ToList()方法,強制查詢求值。(記住,LINQ查詢默認為延遲加載。意思是,直到結果被使用時,查詢才被真正地執行。ToList()方法能強制查詢執行)。在Dbcontext(譯注:其實是它的子類EFRecipesEntities)中枚舉所有的工人和所有的相關的嚴重事故。 匿名類型不會把accidents附加到workers上,但是通過把它們帶到上下文中,實體框架會填充導航屬性,將每一個嚴重事故集合accidents附加到合適的worker上。這個過程一般叫做:Entity Span。這是一個強大而微妙的,發生在實體框架實例化實體類型及它們之間關系的幕后的副作用。

  我們關閉了延遲加載,以便讓accidents在我們的過濾查詢中加載(我們將在第5章討論延遲加載)。如果打開延遲加載,所有的accidents將在我們引用worker的accidents時才加載。這將導致過慮失敗。

  我們一旦有了結果集合,就可以枚舉並打印出每個worker和它的accidents信息。如果一個worker沒有任何嚴重的accidents,我們打印none來指示他們的安全記錄。

  

 

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

謝謝大家的持續關注,我的博客地址:http://www.cnblogs.com/VolcanoCloud/

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM