《Entity Framework 6 Recipes》中文翻譯系列 (19) -----第三章 查詢之使用位操作和多屬性連接(join)


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

3-16  過濾中使用位操作

問題

  你想在查詢的過濾條件中使用位操作。

解決方案

  假設你有一個實體類型,它有一個你想用來做位標識的整型屬性。你將使用這個屬性中的bit位來表示實體中特殊屬性存在與否(譯注:作者想表達的是,bit中位為0或1時,實體的類型就會不一樣)。例如,假設你有一個表示當地畫廊的贊助者(patrons)實體,一些贊助者直接捐款(contribute money),一些在畫廊里當志願者(volunteer),一些服務於董事會(board of directors)。一些贊助者不止提供一種方式來贊助畫廊。一個包含此實體的模型,如圖3-17所示。

 

圖3-17  實體類型Patron,有一個SponsorType屬性,它被用作一個用來指示Patron贊助類型的位標識集合

 

  我們想通過贊助者(patron)提供的贊助類型來過濾查詢。按代碼清單3-34來實現我們的要求。

代碼清單3-34. 在查詢中使用位操作

 1  static void Main(string[] args)
 2         {
 3             RunExample();
 4         }
 5 
 6         [Flags]
 7         public enum SponsorTypes
 8         {
 9             None = 0,
10             ContributesMoney = 1,
11             Volunteers = 2,
12             IsABoardMember = 4
13         };
14 
15         static void RunExample()
16         {
17             using (var context = new EFRecipesEntities())
18             {
19                 // 刪除之前的測試數據
20                 context.Database.ExecuteSqlCommand("delete from chapter3.patron");
21                 // 添加新的測試數據
22                 context.Patrons.Add(new Patron
23                 {
24                     Name = "Jill Roberts",
25                     SponsorType = (int)SponsorTypes.ContributesMoney
26                 });
27                 context.Patrons.Add(new Patron
28                 {
29                     Name = "Ryan Keyes",
30                     //注意位操作符中的OR操作符'|'的用法
31                     SponsorType = (int)(SponsorTypes.ContributesMoney |
32                                         SponsorTypes.IsABoardMember)
33                 });
34                 context.Patrons.Add(new Patron
35                 {
36                     Name = "Karen Rosen",
37                     SponsorType = (int)SponsorTypes.Volunteers
38                 });
39                 context.Patrons.Add(new Patron
40                 {
41                     Name = "Steven King",
42                     SponsorType = (int)(SponsorTypes.ContributesMoney |
43                                         SponsorTypes.Volunteers)
44                 });
45                 context.SaveChanges();
46             }
47 
48             using (var context = new EFRecipesEntities())
49             {
50                 Console.WriteLine("Using LINQ...");
51                 var sponsors = from p in context.Patrons
52                                //注意位操作符中的AND操作符'&'的用法
53                                where (p.SponsorType &
54                                       (int)SponsorTypes.ContributesMoney) != 0
55                                select p;
56                 Console.WriteLine("Patrons who contribute money");
57                 foreach (var sponsor in sponsors)
58                 {
59                     Console.WriteLine("\t{0}", sponsor.Name);
60                 }
61             }
62 
63             using (var context = new EFRecipesEntities())
64             {
65                 Console.WriteLine("\nUsing Entity SQL...");
66                 var esql = @"select value p from Patrons as p
67                              where BitWiseAnd(p.SponsorType, @type) <> 0";
68                 var sponsors = ((IObjectContextAdapter)context).ObjectContext.CreateQuery<Patron>(esql,
69                    new ObjectParameter("type", (int)SponsorTypes.ContributesMoney));
70                 Console.WriteLine("Patrons who contribute money");
71                 foreach (var sponsor in sponsors)
72                 {
73                     Console.WriteLine("\t{0}", sponsor.Name);
74                 }
75             }
76             Console.WriteLine("\nPress <enter> to continue...");
77             Console.ReadLine();
78         }

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

Using LINQ...
Patrons who contribute money
Jill Roberts
Ryan Keyes
Steven King
Using Entity SQL...
Patrons who contribute money
Jill Roberts
Ryan Keyes
Steven King

原理

  在我們的模型中,實體類型Patron,將多個位標識打包在一個單獨的整形屬性中。一個贊助者(patron)可以用多種方式贊助(sponsor)畫廊。每種贊助類型用SponsorType屬性中的不同的位來表示,我們可以創建一個enum類型來表示每種贊助方式。我們為每種類型分配2的整數冪作為它的值。這意味中每個類型在SponsorType屬性中都有確定的一個位。(譯注:整型在C#中占用32位bit,2的二進制表示為 00000000000000000000000000000010,它在示例中表示 志願者(Volunteers),4的二進制表示為00000000000000000000000000000100,它在示例中表示 董事會成員(IsABoardMember))。

  當插入patrons時,我們分配贊助類型給SponsorType屬性,對於不止一種方式(類型)贊助畫廊的贊助者,我們簡單地使用OR操作符(|)將不同的方式合並起來。

  對於LINQ查詢,我們使用了AND位操作符(&),從SponsorType屬性值中提取ContributesMoney(捐錢)這種贊助方式的位。如果結果為非零,那么這個贊助者就有ContributesMoney標識。如果我們想查詢不止一種贊助方式(類型)的贊助者,得在我們使用位操作符AND(&)來提取標識位之前,使用OR來連接所有我們感興趣的SponsorType.

  第二種方法演示了,使用Entity SQL的方式。我們使用函數BitWiseAnd()來提取標識位。Entity SQL支持完整的位操作函數。

 

 

3-17  多列連接(Join)

問題

  你想通過多個屬性來連接(join)兩個實體。

解決方案

  假設你有一個如圖3-18所示的模型。Account(賬戶)實體類型與Order(訂單)實體類型有一對多關聯。每個賬戶可能有多個訂單,然而,一個訂單只能關聯到一個確切的賬戶上。你想查找所有的快遞到與賬號的city,state相同的訂單。

圖3-18 一個包含Account實體類型和與之關聯的Order實體的模型

 

  該示例使用Code-First方法,在代碼清單3-35中,我們創建了實體類型。

代碼清單3-35. 實體類型Account和Order

 public class Account
    {
        public Account()
        {
            Orders = new HashSet<Order>();
        }
        
        public int AccountId { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public virtual ICollection<Order> Orders { get; set; }
    }

 public class Order
    {
        public int OrderId { get; set; }
        public Decimal Amount { get; set; }
        public int AccountId { get; set; }
        public string ShipCity { get; set; }
        public string ShipState { get; set; }
        public virtual Account Account { get; set; }
    }

接下來,代碼清單3-36中創建了上下文對象,它是用Code-First方法訪問實體框架功能的入口。

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

 public class EFRecipesEntities : DbContext
    {
        public EFRecipesEntities()
            : base("ConnectionString") {}

        public DbSet<Order> Orders { get; set; }
        public DbSet<Account> Accounts { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Account>().ToTable("Chapter3.Account");
            modelBuilder.Entity<Order>().ToTable("Chapter3.Order");

            base.OnModelCreating(modelBuilder);
        }
    }

  使用代碼清單3-37查找訂單。

代碼清單3-37. 使用多屬性連接(Join)來查找所有快遞到與賬號的City和State相同的訂單。

 1  using (var context = new EFRecipesEntities())
 2             {
 3                 //刪除之前的測試數據
 4                 context.Database.ExecuteSqlCommand("delete from chapter3.[order]");
 5                 context.Database.ExecuteSqlCommand("delete from chapter3.account");
 6                 //添加新的測試數據
 7                 var account1 = new Account { City = "Raytown", State = "MO" };
 8                 account1.Orders.Add(new Order
 9                 {
10                     Amount = 223.09M,
11                     ShipCity = "Raytown",
12                     ShipState = "MO"
13                 });
14                 account1.Orders.Add(new Order
15                 {
16                     Amount = 189.32M,
17                     ShipCity = "Olathe",
18                     ShipState = "KS"
19                 });
20 
21                 var account2 = new Account { City = "Kansas City", State = "MO" };
22                 account2.Orders.Add(new Order
23                 {
24                     Amount = 99.29M,
25                     ShipCity = "Kansas City",
26                     ShipState = "MO"
27                 });
28 
29                 var account3 = new Account { City = "North Kansas City", State = "MO" };
30                 account3.Orders.Add(new Order
31                 {
32                     Amount = 102.29M,
33                     ShipCity = "Overland Park",
34                     ShipState = "KS"
35                 });
36                 context.Accounts.Add(account1);
37                 context.Accounts.Add(account2);
38                 context.Accounts.Add(account3);
39                 context.SaveChanges();
40             }
41 
42             using (var context = new EFRecipesEntities())
43             {
44                 var orders = from o in context.Orders
45                              join a in context.Accounts on
46                                  // 使用匿名類型來構造一個復合的查詢表達式
47                                  new { Id = o.AccountId, City = o.ShipCity, State = o.ShipState }
48                                  equals
49                                  new { Id = a.AccountId, City = a.City, State = a.State }
50                              select o;
51 
52                 Console.WriteLine("Orders shipped to the account's city, state...");
53                 foreach (var order in orders)
54                 {
55                     Console.WriteLine("\tOrder {0} for {1}", order.AccountId.ToString(),
56                         order.Amount.ToString());
57                 }
58             }
59 
60             Console.WriteLine("\nPress <enter> to continue...");
61             Console.ReadLine();

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

Orders shipped to the account's city, state...
Order 31 for $223.09
Order 32 for $99.29

 

原理

  為了解決這個問題,你可以先查找出所有的accounts,然后通過比較Orders集合中的每個訂單,並找出與account的state和city一樣的訂單。對於只有少量account的情況,這可能是一個合理的解決方案。但是,一般情況下,最好的解決方案是,把這類處理放在存儲層去,因為在存儲層會更有效率。

  一開始,Account和Order通過AccountId屬性連接在一起,然而,在這個解決方案中,我們通過在equals從句的兩邊分別創建一個匿名類型明確地形成一個連接(Join)。連接(Join)實體的屬性數量多於一個時,需要用到匿名構造。 我們要確保兩邊的匿名類型是相同的,必須要有相同的屬性,相同屬性定義順序。這里,我們明確地在數據庫中的兩張表間創建了一個內連接(inner-join),意味着,因為連接條件,寄往別cities和state的orders將不會包含在結果中。

 

  至此,第三章就到此結束。下一篇我們將進行第四章的學習。感謝你的閱讀和學習。

 

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

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

 


免責聲明!

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



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