《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/