翻譯的初衷以及為什么選擇《Entity Framework 6 Recipes》來學習,請看本系列開篇
3-10應用左連接
問題
你想使用左外連接來合並兩個實體的屬性。
解決方案
假設你有如圖3-11所示的模型。
圖3-11 一個包含Product和TopSelling的模型
暢銷產品有一個與之關聯的TopSelling實體。當然,不是所有的產品都是暢銷產品。這就是為什么關系為零或者1。當一個產品為暢銷產品時,與之關聯的topSelling實體包含一個用戶評級。你想查找和呈現所有的產品,和與之關聯的熱銷榜單實體,即使產品不是一個暢銷產品。如果產品沒有相關聯的熱銷榜單,我們就簡單地用0來設置用戶評級。 在數據庫中,這叫做左外連接。
代碼清單3-22演示了三種略有不同的方法來解決這個問題。
代碼清單3-22. 兩個實體間的左外連接
1 using (var context = new EFRecipesEntities()) 2 { 3 // 刪除之前的測試數據 4 context.Database.ExecuteSqlCommand("delete from chapter3.topselling"); 5 context.Database.ExecuteSqlCommand("delete from chapter3.product"); 6 // 添加新的測試數據 7 // 注意p1沒有與之關聯的TopSelling實體 8 var p1 = new Product { Name = "Trailrunner Backpack" }; 9 var p2 = new Product 10 { 11 Name = "Green River Tent", 12 TopSelling = new TopSelling { Rating = 3 } 13 }; 14 var p3 = new Product 15 { 16 Name = "Prairie Home Dutch Oven", 17 TopSelling = new TopSelling { Rating = 4 } 18 }; 19 var p4 = new Product 20 { 21 Name = "QuickFire Fire Starter", 22 TopSelling = new TopSelling { Rating = 2 } 23 }; 24 context.Products.Add(p1); 25 context.Products.Add(p2); 26 context.Products.Add(p3); 27 context.Products.Add(p4); 28 context.SaveChanges(); 29 } 30 31 using (var context = new EFRecipesEntities()) 32 { 33 var products = from p in context.Products 34 orderby p.TopSelling.Rating descending 35 select p; 36 Console.WriteLine("All products, including those without ratings"); 37 38 foreach (var product in products) 39 { 40 Console.WriteLine("\t{0} [rating: {1}]", product.Name, 41 product.TopSelling == null ? "0" 42 : product.TopSelling.Rating.ToString()); 43 } 44 } 45 46 using (var context = new EFRecipesEntities()) 47 { 48 var products = from p in context.Products 49 join t in context.TopSellings on 50 //注意,我們如何將結果集投影到另一個名為'g'的序列中,以及應用DefaultIfEmpty方法 51 p.ProductID equals t.ProductID into g 52 from tps in g.DefaultIfEmpty() 53 orderby tps.Rating descending 54 select new 55 { 56 Name = p.Name, 57 Rating = tps.Rating == null ? 0 : tps.Rating 58 }; 59 60 Console.WriteLine("\nAll products, including those without ratings"); 61 foreach (var product in products) 62 { 63 Console.WriteLine("\t{0} [rating: {1}]", product.Name, 64 product.Rating.ToString()); 65 } 66 } 67 68 using (var context = new EFRecipesEntities()) 69 { 70 var esql = @"select value p from products as p 71 order by case when p.TopSelling is null then 0 72 else p.TopSelling.Rating end desc"; 73 var products = ((IObjectContextAdapter)context).ObjectContext.CreateQuery<Product>(esql); 74 Console.WriteLine("\nAll products, including those without ratings"); 75 foreach (var product in products) 76 { 77 Console.WriteLine("\t{0} [rating: {1}]", product.Name, 78 product.TopSelling == null ? "0" 79 : product.TopSelling.Rating.ToString()); 80 } 81 } 82 83 Console.WriteLine("\nPress <enter> to continue..."); 84 Console.ReadLine(); 85 }
代碼清單3-22的輸出如下:
Top selling products sorted by rating Prairie Home Dutch Oven [rating: 4] Green River Tent [rating: 3] QuickFire Fire Starter [rating: 2] Trailrunner Backpack [rating: 0]Top selling products sorted by rating Prairie Home Dutch Oven [rating: 4] Green River Tent [rating: 3] QuickFire Fire Starter [rating: 2] Trailrunner Backpack [rating: 0]Top selling products sorted by rating Prairie Home Dutch Oven [rating: 4] Green River Tent [rating: 3] QuickFire Fire Starter [rating: 2] Trailrunner Backpack [rating: 0]
原理
在代碼清單3-22中,我們展示了三種不同的方法來處理這個問題。第一種方法最簡單。因為實體框架會為關聯的實體自動創建join連接,它是基於模型創建時被創建的導航屬性。這兩個實體的關聯為1-0...1,這意味着,實體框架在自動生成SQL查詢時,會在這兩個實體間包含一個左外連接。當product實體被實例化時,任何與之關聯的topSelling也會被實例化。導航屬性topSelling也會被設置為相關聯的實體,如是不存在topSelling就為null。如果給定的產品不存在與之關聯的topSelling實體(也就是說,它沒有被評為暢銷產品)。我們會簡單的分配一個0值給產品的評級。
在某些情況下,你想連接的兩個實體間可能沒有一個關系(對實例而言,是一個導航屬性)。這樣的話,你可以顯式連接實體。把結果集投影到一個匿名類型。我們之所以需要將結果集投影到匿名類型,是因為沒有關聯的兩個實體間沒有導航屬性。否則我們不可以引用相關聯的實體。
在第二段查詢的代碼中演示了這種方法。通過鍵ProductId連接兩個實體,然后把結果集投影到一個新的名為'g'的序列中。隨后,我們在g上應用DefaultIfEmpty()方法,當g為空時,它會用null值填充。毫無疑問,當SQL語句被產生時,兩個實體間將會包含一個左外連接。我們通過一個orderby從句,讓結果集根據rating排序。最后,我們將結果投影到一個匿名類型。
在第三中方法中,我們展示了在Entity SQL中,如何更明確地使用左外連接。在查詢中嵌入一個Entity SQL語句。
3-11通過派生類排序
問題
當你使用TPH繼承映射時,你想通過派生類來給結果集排序。
解決方案
假設你有如圖3-12所示的模型。
圖3-12 使用TPH繼承映射的模型,包含三個派生類弄型
這個模型使用TPH繼承映射,它是實體框架一個特性,TPH創建這樣的一個繼承結構,父類和子類都源至數據庫中的同一張表。
在這個示例中,Media實體有一個屬性名為Mediatype,它被用作TPH結構中的鑒別屬性。MediaType的值決定着數據庫表中的行被映射到哪個派生類型(Article,Picture,或者Video)。鑒別列的值為1時代表Article類型,為2時代表Video類型,為3時代表Picture類型。因為這個屬性被用來決定派生類型,它將不在Media實體中顯示。
我們在代碼清單3-23中使用Code-First,創建了實體類,為了保持示例的簡單性,我們只創建了空的派生子對象。因為我只是為了演示在查詢中通過派生類型來排序。
代碼清單3-23. 父類和子類實體類型
1 public class Media 2 { 3 public int MediaId { get; set; } 4 public string Title { get; set; } 5 } 6 public class Article : Media 7 { 8 } 9 public class Picture : Media 10 { 11 } 12 public class Video : Media 13 { 14 }
接下來,在代碼清單3-24中,我們創建了數據庫上下文對象,它是我們憑借實體框架中Code-First方法的入口。注意,如何在OnModelCreating方法中,使用FluentAPI(換句話說就是將擴展方法鏈接起來創建一個操作)。顯式映射鑒別列,MediaType,到子類型。
代碼清單3-24 DbContext對象
1 public class EFRecipesEntities : DbContext 2 { 3 public EFRecipesEntities() 4 : base("ConnectionString") {} 5 6 public DbSet<Media> Media { get; set; } 7 8 protected override void OnModelCreating(DbModelBuilder modelBuilder) 9 { 10 modelBuilder.Entity<Media>().ToTable("Chapter3.Media"); 11 12 13 modelBuilder.Entity<Media>().Map<Article>(x => x.Requires("MediaType").HasValue(1)); 14 modelBuilder.Entity<Media>().Map<Picture>(x => x.Requires("MediaType").HasValue(2)); 15 modelBuilder.Entity<Media>().Map<Video>(x => x.Requires("MediaType").HasValue(3)); 16 17 base.OnModelCreating(modelBuilder); 18 } 19 }
隨着Code-First的構件被創建,我們將查詢模型中所有的media,並通過派生類型:Article,Video,和Picture進行排序。如代碼清單3-25所示。
代碼清單3-25.在TPH繼承映射中通過類型排序
1 using (var context = new EFRecipesEntities()) 2 { 3 //刪除之前的測試數據 4 context.Database.ExecuteSqlCommand("delete from chapter3.Media"); 5 // 添加新的測試數據 6 context.Media.Add(new Article 7 { 8 Title = "Woodworkers' Favorite Tools" 9 }); 10 context.Media.Add(new Article 11 { 12 Title = "Building a Cigar Chair" 13 }); 14 context.Media.Add(new Video 15 { 16 Title = "Upholstering the Cigar Chair" 17 }); 18 context.Media.Add(new Video 19 { 20 Title = "Applying Finish to the Cigar Chair" 21 }); 22 context.Media.Add(new Picture 23 { 24 Title = "Photos of My Cigar Chair" 25 }); 26 context.Media.Add(new Video 27 { 28 Title = "Tour of My Woodworking Shop" 29 }); 30 context.SaveChanges(); 31 } 32 33 using (var context = new EFRecipesEntities()) 34 { 35 var allMedium = from m in context.Media 36 let mediumtype = m is Article 37 ? 1 38 : m is Video ? 2 : 3 39 orderby mediumtype 40 select m; 41 Console.WriteLine("All Medium sorted by type...\n"); 42 foreach (var medium in allMedium) 43 { 44 Console.WriteLine("Title: {0} [{1}]", medium.Title, medium.GetType().Name); 45 } 46 } 47 48 Console.WriteLine("\nPress <enter> to continue..."); 49 Console.ReadLine(); 50 }
代碼清單3-25的輸出如下:
1 All Media sorted by type... 2 Title: Woodworkers' Favorite Tools [Article] 3 Title: Building a Cigar Chair [Article] 4 Title: Upholstering the Cigar Chair [Video] 5 Title: Applying Finish to the Cigar Chair [Video] 6 Title: Tour of My Woodworking Shop [Video] 7 Title: Photos of My Cigar Chair [Picture]
原理
當我們使用TPH繼承映射時,我們憑借表中一列來區別給定的行代表的派生類型。這一列,通常被稱作鑒別列。它不能被映射到實體中的屬性。因為我們沒有屬性包含鑒別值 ,所以,需要創建一個變量來保存鑒別值以便我們進行排序。為了實現這個目的,我們使用了LINQ中的let從句。它創建了一個mediatype變量。我們通過條件語句來將整型的鑒別值分配給這個變量。Article類型的分配1,Video類型的分配2,其它類型的分配3。這里的其它類型其實就是Picture類型,因為沒有別的派生類型了。
實體框架交流QQ群: 458326058,歡迎有興趣的朋友加入一起交流
謝謝大家的持續關注,我的博客地址:http://www.cnblogs.com/VolcanoCloud/