• 《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("	{0}", sponsor.Name);
    60                 }
    61             }
    62 
    63             using (var context = new EFRecipesEntities())
    64             {
    65                 Console.WriteLine("
    Using 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("	{0}", sponsor.Name);
    74                 }
    75             }
    76             Console.WriteLine("
    Press <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("	Order {0} for {1}", order.AccountId.ToString(),
    56                         order.Amount.ToString());
    57                 }
    58             }
    59 
    60             Console.WriteLine("
    Press <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/

  • 相关阅读:
    如何写一个简单的HTTP服务器(重做版)
    如何写一个简单的shell
    Linux守护进程
    字节序:大端法和小端法
    学习计算机需要看哪些经典书?
    IA32寄存器与x86-64寄存器的区别
    C++中extern关键字用法小结
    操作系统中陷阱,中断和异常的区别
    排查CPU占用过高的问题
    git初始化、获取git仓库
  • 原文地址:https://www.cnblogs.com/VolcanoCloud/p/4515073.html
Copyright © 2020-2023  润新知