《Entity Framework 6 Recipes》中文翻譯系列 (16) -----第三章 查詢之左連接和在TPH中通過派生類排序


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

 


免責聲明!

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



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