《Entity Framework 6 Recipes》中文翻译系列 (18) -----第三章 查询之结果集扁平化和多属性分组

翻译的初衷以及为何选择《Entity Framework 6 Recipes》来学习,请看本系列开篇html

3-14  结果集扁平化

问题sql

  你有一对多关联的两个实体,你想经过一个查询,获取关联中的两个实体的扁平化投影。扁平化或者叫压缩,这是不规范的叫法。它是指一个有父类和子类的对象图,被投影到一个单独的类中。数据库

解决方案app

  假设你有一对拥有一对多关联的实体,如图3-15所示的模型。cors

图3-15 模型中,一个表明助理的Associate的实体类型和一个表明助理工资历史的AssociateSalary实体框架

 

  你想在一个查询中获取全部的associates 和他们的工资历史,可能有些新员工在系统尚未工资记录。 你但愿查询结果集中能包含这些关联。ide

  请按代码清单3-30的方式,查询模型并获取你想要的结果集。函数

代码清单3-30. 使用LINQ和Entity SQL扁平化结果集学习

 1 using (var context = new EFRecipesEntities())
 2             {
 3                 // 删除以前的测试数据
 4                 context.Database.ExecuteSqlCommand("delete from chapter3.associatesalary");
 5                 context.Database.ExecuteSqlCommand("delete from chapter3.associate");
 6                 // 添加新的测试数据
 7                 var assoc1 = new Associate { Name = "Janis Roberts" };
 8                 var assoc2 = new Associate { Name = "Kevin Hodges" };
 9                 var assoc3 = new Associate { Name = "Bill Jordan" };
10                 var salary1 = new AssociateSalary
11                 {
12                     Salary = 39500M,
13                     SalaryDate = DateTime.Parse("8/4/09")
14                 };
15                 var salary2 = new AssociateSalary
16                 {
17                     Salary = 41900M,
18                     SalaryDate = DateTime.Parse("2/5/10")
19                 };
20                 var salary3 = new AssociateSalary
21                 {
22                     Salary = 33500M,
23                     SalaryDate = DateTime.Parse("10/08/09")
24                 };
25                 assoc2.AssociateSalaries.Add(salary1);
26                 assoc2.AssociateSalaries.Add(salary2);
27                 assoc3.AssociateSalaries.Add(salary3);
28                 context.Associates.Add(assoc1);
29                 context.Associates.Add(assoc2);
30                 context.Associates.Add(assoc3);
31                 context.SaveChanges();
32             }
33 
34             using (var context = new EFRecipesEntities())
35             {
36                 Console.WriteLine("Using LINQ...");
37                 var allHistory = from a in context.Associates
38                                  from ah in a.AssociateSalaries.DefaultIfEmpty()
39                                  orderby a.Name
40                                  select new
41                                  {
42                                      Name = a.Name,
43                                      Salary = (decimal?)ah.Salary,
44                                      Date = (DateTime?)ah.SalaryDate
45                                  };
46 
47                 Console.WriteLine("Associate Salary History");
48                 foreach (var history in allHistory)
49                 {
50                     if (history.Salary.HasValue)
51                         Console.WriteLine("{0} Salary on {1} was {2}", history.Name,
52                                            history.Date.Value.ToShortDateString(),
53                                            history.Salary.Value.ToString("C"));
54                     else
55                         Console.WriteLine("{0} --", history.Name);
56                 }
57             }
58 
59             using (var context = new EFRecipesEntities())
60             {
61                 Console.WriteLine("\nUsing Entity SQL...");
62                 var esql = @"select a.Name, h.Salary, h.SalaryDate
63                  from Associates as a outer apply 
64                    a.AssociateSalaries as h order by a.Name";
65                 var allHistory = ((IObjectContextAdapter)context).ObjectContext.CreateQuery<DbDataRecord>(esql);
66                 Console.WriteLine("Associate Salary History");
67                 foreach (var history in allHistory)
68                 {
69                     if (history["Salary"] != DBNull.Value)
70                         Console.WriteLine("{0} Salary on {1:d} was {2:c}", history["Name"],
71                                            history["SalaryDate"], history["Salary"]);
72                     else
73                         Console.WriteLine("{0} --", history["Name"]);
74                 }
75             }
76 
77             Console.WriteLine("\nPress <enter> to continue...");
78             Console.ReadLine();
79         }

  这里的诀窍是,咱们“扁平化”(flatten)层次结构的数据,好比,一个associate和多个 salary输入。代码清单3-30输出以下:测试

1 Using LINQ...
2 Associate Salary History
3 Bill Jordan Salary on 10/8/2009 was $33,500.00
4 Janis Roberts --Kevin Hodges Salary on 8/4/2009 was $39,500.00
5 Kevin Hodges Salary on 2/5/2010 was $41,900.00
6 Using Entity SQL...
7 Bill Jordan Salary on 10/8/2009 was $33,500.00
8 Janis Roberts --Kevin Hodges Salary on 8/4/2009 was $39,500.00
9 Kevin Hodges Salary on 2/5/2010 was $41,900.00

原理

  为了扁平化结果集,咱们使用了3-10中的策略、使用嵌套形式的from从句和DefaultIfEmpty()方法来得到两张表的一个左外链接。方法DefaultIfEmpty()能确保咱们有左边表(Associate 实体)的全部行,即便右边(AssociateSalary实体)没有与它对应的行。咱们将结果集投影到一个匿名类型,当没有AssociateSalary实体与Associate实体对应时,当心属性salary 和 salarydate获得null值。

  对于Entity SQL 解决方案,咱们使用  outer apply 操做符建立Associate实体和AssociateSalary实体之间的匹配。 在SQL Server中,可使用corss和outer apply操做符。

 

3-15  使用多属性分组

问题

  你想在一个查询中使用多属性对结果集进行分组,以至在数据库中执行查询时使用多列进行分组。

解决方案

  假设你有包含一个Enent实体的模型,如图3-16所示。Event有属性name,city和sate。你想经过state和city对events进行分组。

图3-16 包含一个Enent实体的模型,Event有属性name,city和sate属性

 

  在代码清单3-31中,咱们使用Code-First方法建立了实体类。

代码清单3-31.实体类型

1 public class Event
2     {
3         public int EventId { get; set; }
4         public string Name { get; set; }
5         public string State { get; set; }
6         public string City { get; set; }
7     }

  接下来,代码清单3-32中建立了上下文对象,它是用Code-First方法访问实体框架功能的入口。

代码清单3-32.  上下文对象

 1  public class EFRecipesEntities : DbContext
 2     {
 3         public EFRecipesEntities()
 4             : base("ConnectionString") {}
 5 
 6         public DbSet<Event> Events { get; set; }
 7 
 8         protected override void OnModelCreating(DbModelBuilder modelBuilder)
 9         {
10             modelBuilder.Entity<Event>().ToTable("Chapter3.Event");
11             base.OnModelCreating(modelBuilder);
12         }
13     }

  使用代码清单3-33中的代码,获取全部的events并按sate和city对结果集分组

代码清单3-33 多属性分组

 1  using (var context = new EFRecipesEntities())
 2             {
 3                 // 删除以前的测试数据
 4                 context.Database.ExecuteSqlCommand("delete from chapter3.event");
 5                 //添加新的测试数据
 6                 context.Events.Add(new Event
 7                 {
 8                     Name = "TechFest 2010",
 9                     State = "TX",
10                     City = "Dallas"
11                 });
12                 context.Events.Add(new Event
13                 {
14                     Name = "Little Blue River Festival",
15                     State = "MO",
16                     City = "Raytown"
17                 });
18                 context.Events.Add(new Event
19                 {
20                     Name = "Fourth of July Fireworks",
21                     State = "MO",
22                     City = "Raytown"
23                 });
24                 context.Events.Add(new Event
25                 {
26                     Name = "BBQ Ribs Championship",
27                     State = "TX",
28                     City = "Dallas"
29                 });
30                 context.Events.Add(new Event
31                 {
32                     Name = "Thunder on the Ohio",
33                     State = "KY",
34                     City = "Louisville"
35                 });
36                 context.SaveChanges();
37             }
38 
39             using (var context = new EFRecipesEntities())
40             {
41                 Console.WriteLine("Using LINQ");
42                 var results = from e in context.Events
43                               // 使用匿名类型封闭复合key State 和City
44                               group e by new { e.State, e.City } into g
45                               select new
46                               {
47                                   State = g.Key.State,
48                                   City = g.Key.City,
49                                   Events = g
50                               };
51                 Console.WriteLine("Events by State and City...");
52                 foreach (var item in results)
53                 {
54                     Console.WriteLine("{0}, {1}", item.City, item.State);
55                     foreach (var ev in item.Events)
56                     {
57                         Console.WriteLine("\t{0}", ev.Name);
58                     }
59                 }
60             }
61 
62             using (var context = new EFRecipesEntities())
63             {
64                 Console.WriteLine("\nUsing Entity SQL");
65                 var esql = @"select e.State, e.City, GroupPartition(e) as Events
66                  from Events as e
67                  group by e.State, e.City";
68                 var records = ((IObjectContextAdapter)context).ObjectContext.CreateQuery<DbDataRecord>(esql);
69                 Console.WriteLine("Events by State and City...");
70                 foreach (var rec in records)
71                 {
72                     Console.WriteLine("{0}, {1}", rec["City"], rec["State"]);
73                     var events = (List<Event>)rec["Events"];
74                     foreach (var ev in events)
75                     {
76                         Console.WriteLine("\t{0}", ev.Name);
77                     }
78                 }
79             }
80 
81             Console.WriteLine("\nPress <enter> to continue...");
82             Console.ReadLine();

代码清单3-33的输出以下:

Using LINQ
Events by State and City...
Louisville, KY
Thunder on the Ohio
Raytown, MO
Little Blue River Festival
Fourth of July Fireworks
Dallas, TX
TechFest 2010
BBQ Ribs Championship
Using Entity SQL
Events by State and City...
Louisville, KY
Thunder on the Ohio
Raytown, MO
Little Blue River Festival
Fourth of July Fireworks
Dallas, TX
TechFest 2010
BBQ Ribs Championship

 

原理

   在代码清单3-33中,针对这个问题,展现了两种不一样的方法。 第一种方法使用LINQ和group by 操做符按sate和city对结果集进行分组。当用group by进行多属性分组时, 咱们建立了一个匿名类型对数据进行分组。 使用into从句将分组放到g中,它是咱们存放查询结果集的第二个序列。

  咱们把结果集从g中投影到第二个匿名类型中,经过从分组key的字段State(第一个匿名类型中)获取State值,从分组key的字段City中获取 City值,对于events 我只是简单地把分组的所有成员分配给它。

  对于Entity SQL方法,咱们只能投影group by 从句使用的列、常量或者从聚合函数计算获得的值。在咱们示例中,咱们投影state、city和分组中的events.

 

 

实体框架交流QQ群:  458326058,欢迎有兴趣的朋友加入一块儿交流

谢谢你们的持续关注,个人博客地址:http://www.cnblogs.com/VolcanoCloud/

相关文章
相关标签/搜索