《Entity Framework 6 Recipes》中文翻译系列 (17) -----第三章 查询之分页、过滤和使用DateTime中的日期部分分组
翻译的初衷以及为什么选择《Entity Framework 6 Recipes》来学习,请看本系列开篇
3-12 分页和过滤
问题
你想使用分页和过滤来创建查询。
解决方案
假设你有如图3-13所示的模型,模型中有一个Custormer实体类型。
图3-13 包含一个Customer实体类型的模型
你有一个基于过滤条件来显示客户信息的应用。你的公司有许多客户(也许数百万!),为了保证尽可能响应的用户体验,你想在每一页上只显示一定数量的客户。创建一个查询,它能过虑客户并按页返回可控数量的结果集。如代码清单3-26所示。
代码清单3-26. 一个包含过滤和分页的查询
1 using (var context = new EFRecipesEntities()) 2 { 3 // 删除之前的数据 4 context.Database.ExecuteSqlCommand("delete from chapter3.customer"); 5 // 添加新的测试数据 6 context.Customers.Add(new Customer 7 { 8 Name = "Roberts, Jill", 9 Email = "jroberts@abc.com" 10 }); 11 context.Customers.Add(new Customer 12 { 13 Name = "Robertson, Alice", 14 Email = "arob@gmail.com" 15 }); 16 context.Customers.Add(new Customer 17 { 18 Name = "Rogers, Steven", 19 Email = "srogers@termite.com" 20 }); 21 context.Customers.Add(new Customer 22 { 23 Name = "Roe, Allen", 24 Email = "allenr@umc.com" 25 }); 26 context.Customers.Add(new Customer 27 { 28 Name = "Jones, Chris", 29 Email = "cjones@ibp.com" 30 }); 31 context.SaveChanges(); 32 } 33 34 35 using (var context = new EFRecipesEntities()) 36 { 37 string match = "Ro"; 38 int pageIndex = 0; 39 int pageSize = 3; 40 41 var customers = context.Customers.Where(c => c.Name.StartsWith(match)) 42 //var customers = context.Customers.Where(c => c.Name.Contains(match)) 43 .OrderBy(c => c.Name) 44 .Skip(pageIndex * pageSize) 45 .Take(pageSize); 46 Console.WriteLine("Customers Ro*"); 47 foreach (var customer in customers) 48 { 49 Console.WriteLine("{0} [email: {1}]", customer.Name, customer.Email); 50 } 51 } 52 53 using (var context = new EFRecipesEntities()) 54 { 55 string match = "Ro%"; 56 int pageIndex = 0; 57 int pageSize = 3; 58 59 var esql = @"select value c from Customers as c 60 where c.Name like @Name 61 order by c.Name 62 skip @Skip limit @Limit"; 63 Console.WriteLine("\nCustomers Ro*"); 64 var customers = ((IObjectContextAdapter)context).ObjectContext.CreateQuery<Customer>(esql, new[] 65 { 66 new ObjectParameter("Name",match), 67 new ObjectParameter("Skip",pageIndex * pageSize), 68 new ObjectParameter("Limit",pageSize) 69 }); 70 foreach (var customer in customers) 71 { 72 Console.WriteLine("{0} [email: {1}]", customer.Name, customer.Email); 73 } 74 } 75 76 Console.WriteLine("\nPress <enter> to continue..."); 77 Console.ReadLine();
代码清单3-26的输出如下:
Customers Ro* Roberts, Jill [email: jroberts@abc.com] Robertson, Alice [email: arob@gmail.com] Roe, Allen [email: allenr@umc.com] Customers Ro* Roberts, Jill [email: jroberts@abc.com] Robertson, Alice [email: arob@gmail.com] Roe, Allen [email: allenr@umc.com]
原理
在代码清单3-26中,针对这个问题,我们展示了不同的方法。在第一种方法中,我们使用了LINQ to Entities扩展方法创建了一个LINQ查询。我们使用Where()方法过滤结果集,过虑条件为,姓以“Ro“开头。因为我们在lambda表达工中使用了扩展方法StartsWith()。我们不需要使用SQL的通配符表达式“Ro%"。
过滤后,我们使用OrderBy()方法对结果集排序,排序后的结果集通过方法Skip()来获取。我们使用Skip()方法跳过PageIndex页,每页的大小为PageSize. 使用Take()方法来获取受限的结果集(译注:从结果集获取指定页大小的记录数),我们只需要获取结果集中的一页。
注意,在代码块中,我们使用LINQ扩展方法创建了一个完整的查询,而不是我们之前看到的SQL查询表达式。Skip()和Take()方法只在扩展方法中公布,不是查询语法。
第二种方法,我们构建了一个完整的参数化的Entity SQL表达式,这也许是你最熟悉的方式,但是这在使用字符串和可执行代码(C#)来表示查询的两种方式间产生了固有的不匹配风险。
3-13 按日期分组
问题
你有一个包含DateTime类型属性的实体,你想通过DateTime类型属性的Date部分来对实体的实例进行分组。
解决方案
假设你有如图3-14所示的模型,模型中有一个Registration实体类型,该实体类型包含一个DateTime类型的属性。
图3-14 模型中有一个Registration实体类型,该实体类型包含一个DateTime类型的属性
该示例使用Code-First方法,在代码清单3-27中,我们创建了一些实体。
代码清单3-27. Registration实体类型
1 public class Registration 2 { 3 public int RegistrationId { get; set; } 4 public string StudentName { get; set; } 5 public DateTime? RegistrationDate { get; set; } 6 }
接下来,代码清单3-28中创建了上下文对象,它是用Code-First方法访问实体框架功能的入口。
代码清单3-28. 上下文对象
1 public class EFRecipesEntities : DbContext 2 { 3 public EFRecipesEntities() 4 : base("ConnectionString") {} 5 public DbSet<Registration> Registrations { get; set; } 6 protected override void OnModelCreating(DbModelBuilder modelBuilder) 7 { 8 modelBuilder.Entity<Registration>().ToTable("Chapter3.Registration"); 9 base.OnModelCreating(modelBuilder); 10 } 11 }
我们使用RegistrationDate属性的Date部分对所有的registrations进行分组,你可能会对LINQ中的 group by RegistrationDate.Date动心.虽然能过编译,但是你仍会得到一个运行时错误,该错误描述Date不能转换成SQL。 为了能使用RegistrationDate属性的Date部分来分组,请看代码清单3-29.
代码清单3-29. 使用DateTime类型属性的Date部分对实例进行分组
1 using (var context = new EFRecipesEntities()) 2 { 3 // 删除之前的测试数据 4 context.Database.ExecuteSqlCommand("delete from chapter3.registration"); 5 // 添加新的测试数据 6 context.Registrations.Add(new Registration 7 { 8 StudentName = "Jill Rogers", 9 RegistrationDate = DateTime.Parse("12/03/2009 9:30 pm") 10 }); 11 context.Registrations.Add(new Registration 12 { 13 StudentName = "Steven Combs", 14 RegistrationDate = DateTime.Parse("12/03/2009 10:45 am") 15 }); 16 context.Registrations.Add(new Registration 17 { 18 StudentName = "Robin Rosen", 19 RegistrationDate = DateTime.Parse("12/04/2009 11:18 am") 20 }); 21 context.Registrations.Add(new Registration 22 { 23 StudentName = "Allen Smith", 24 RegistrationDate = DateTime.Parse("12/04/2009 3:31 pm") 25 }); 26 context.SaveChanges(); 27 } 28 29 using (var context = new EFRecipesEntities()) 30 { 31 var groups = from r in context.Registrations 32 // 凭借内置的TruncateTime函数提取Date部分 33 group r by DbFunctions.TruncateTime(r.RegistrationDate) 34 into g 35 select g; 36 foreach (var element in groups) 37 { 38 Console.WriteLine("\nRegistrations for {0}", 39 ((DateTime)element.Key).ToShortDateString()); 40 foreach (var registration in element) 41 { 42 Console.WriteLine("\t{0}", registration.StudentName); 43 } 44 } 45 } 46 47 Console.WriteLine("\nPress <enter> to continue..."); 48 Console.ReadLine();
代码清单3-29输出如下:
Registrations for 12/3/2009 Jill Rogers Steven Combs Registrations for 12/4/2009 Robin Rosen Allen Smit
原理
对registrations分组的分组键是通过Truncate()函数提RegistrationDate属性中的Date部分。这是实体框架的内置函数,包含在DbFunctions类中,它只提取DateTime中的Date部分。内置的DbFunctions类,包含着大量的,格式化、聚合、字符串操作、日期和数字服务。它在命名空间System.Data.Entity中。遗留类,EntityFunctios,是在EF6之前的版本中使用的,它仍然能在EF6中使用,但你会得到一个编译警告,它建议你使用最亲的DbFunctions类。我们将在11章继续讨论它。
实体框架交流QQ群: 458326058,欢迎有兴趣的朋友加入一起交流
谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/VolcanoCloud/