翻譯的初衷以及為什么選擇《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/