《Entity Framework 6 Recipes》中文翻译系列 (15) -----第三章 查询之与列表值比较和过滤关联实体
翻译的初衷以及为什么选择《Entity Framework 6 Recipes》来学习,请看本系列开篇
3-8与列表值比较
问题
你想查询一个实体,条件是给定的列表中包含指定属性的值。
解决方案
假设你有如图3-9所示的模型。
图3-9 包含books和它的categoryes的模型
你想查找给定目录列表中的所有图书。在代码清单3-16中使用LINQ和Entity SQL来实现这上功能。
代理清单3-16. 使用LINQ和Entity SQL来查找给定目录列表中的所有图书
1 using (var context = new EFRecipesEntities()) 2 { 3 // 删除之前的测试数据 4 context.Database.ExecuteSqlCommand("delete from chapter3.book"); 5 context.Database.ExecuteSqlCommand("delete from chapter3.category"); 6 // 添加新的测试数据 7 var cat1 = new Category {Name = "Programming"}; 8 var cat2 = new Category {Name = "Databases"}; 9 var cat3 = new Category {Name = "Operating Systems"}; 10 context.Books.Add(new Book {Title = "F# In Practice", Category = cat1}); 11 context.Books.Add(new Book {Title = "The Joy of SQL", Category = cat2}); 12 context.Books.Add(new Book 13 { 14 Title = "Windows 7: The Untold Story", 15 Category = cat3 16 }); 17 context.SaveChanges(); 18 } 19 20 using (var context = new EFRecipesEntities()) 21 { 22 Console.WriteLine("Books (using LINQ)"); 23 var cats = new List<string> {"Programming", "Databases"}; 24 var books = from b in context.Books 25 where cats.Contains(b.Category.Name) 26 select b; 27 foreach (var book in books) 28 { 29 Console.WriteLine("'{0}' is in category: {1}", book.Title, 30 book.Category.Name); 31 } 32 } 33 34 using (var context = new EFRecipesEntities()) 35 { 36 Console.WriteLine("\nBooks (using eSQL)"); 37 var esql = @"select value b from Books as b 38 where b.Category.Name in {'Programming','Databases'}"; 39 var books = ((IObjectContextAdapter) context).ObjectContext.CreateQuery<Book>(esql); 40 foreach (var book in books) 41 { 42 Console.WriteLine("'{0}' is in category: {1}", book.Title, 43 book.Category.Name); 44 } 45 } 46 47 Console.WriteLine("\nPress <enter> to continue..."); 48 Console.ReadLine(); 49 }
代码清单3-16的输出如下:
Books (using LINQ) 'F# In Practice' is in category: Programming 'The Joy of SQL' is in category: Databases Books (using ESQL) 'F# In Practice' is in category: Programming 'The Joy of SQL' is in category: Databases
原理
在LINQ查询中,我构造了一个简单的目录名列表,它使用LINQ查询操作符Contains。细心的读者可以注意了,我使用cats集合,并判断它是否包含某一目录名。实体框架将Containts从句转换成SQL语句中的in从句。如代码清单3-17所示:
代码清单3-17. 代码清单3-16中LINQ查询表达式对应的SQL语句
1 SELECT 2 [Extent1].[BookId] AS [BookId], 3 [Extent1].[Title] AS [Title], 4 [Extent1].[CategoryId] AS [CategoryId] 5 FROM [chapter3].[Books] AS [Extent1] 6 LEFT OUTER JOIN [chapter3].[Category] AS [Extent2] ON [Extent1].[CategoryId] = [Extent2].[CategoryId] 7 WHERE [Extent2].[Name] IN (N'Programming',N'Databases')
有趣的是,代码清单3-17中的SQL语句,没有为从句的项使用参数。 这不同于LINQ to SQL中产生的代码,列表中的项会被参数化。超过SQL Server的参数限制的风险,会使代码不能运行。
如果我们需要查找出给定目录列表中的所有书,列表目录中包含未分类的目录,我们只需在目录列表中简单地使用null值。产生的SQL语句,如代码清单3-18所示。
代码清单3-18.代码清单3-16中LINQ查询表达式对应的SQL语句,但是目录列表中多了一上null值。
1 SELECT 2 [Extent1].[BookId] AS [BookId], 3 [Extent1].[Title] AS [Title], 4 [Extent1].[CategoryId] AS [CategoryId] 5 FROM [chapter3].[Books] AS [Extent1] 6 LEFT OUTER JOIN [chapter3].[Category] AS [Extent2] ON [Extent1].[CategoryId] = [Extent2].[CategoryId] 7 WHERE [Extent2].[Name] IN (N'Programming',N'Databases') 8 OR [Extent2].[Name] IS NULL
相同地,我们包含了一个使用Entity SQL的查询版本,它显式地包含了一个SQL IN 从句。
3-9过滤关联实体
问题
你想获取一部分,不是全部关联实体。
解决方案
假设你有如图3-10所示的模型。
图3-10 包含Worker和他们的Accidents的模型
在模型中,一个Worker有零个或是多个accidents.每个事故按严重性分类,我们要获取所有的工人,对于与之关联的事故,只对严重事故感兴趣,事故的严重性大于2.
在示例中我们使用了Code-First,在代码清单3-19中,创建了实体类Worker和Accidentd.
代码清单3-19. 实体类
1 public class Worker 2 { 3 public Worker() 4 { 5 Accidents = new HashSet<Accident>(); 6 } 7 8 public int WorkerId { get; set; } 9 public string Name { get; set; } 10 11 public virtual ICollection<Accident> Accidents { get; set; } 12 } 13 14 15 public class Accident 16 { 17 public int AccidentId { get; set; } 18 public string Description { get; set; } 19 public int? Severity { get; set; } 20 public int WorkerId { get; set; } 21 22 public virtual Worker Worker { get; set; } 23 }
接下来,我们在代码清单3-20中创建Code-First中使用的上下文对象。
代码清单3-20. 上下文对象
1 public class EFRecipesEntities : DbContext 2 { 3 public EFRecipesEntities() 4 : base("ConnectionString") {} 5 6 public DbSet<Accident> Accidents { get; set; } 7 public DbSet<Worker> Workers { get; set; } 8 9 protected override void OnModelCreating(DbModelBuilder modelBuilder) 10 { 11 modelBuilder.Entity<Accident>().ToTable("Chapter3.Accident"); 12 modelBuilder.Entity<Worker>().ToTable("Chapter3.Worker"); 13 base.OnModelCreating(modelBuilder); 14 } 15 }
使用代码清单3-21中的模式,获取所有的worders,和严重事故。
代码清单3-21 使用CreateSourceQuery和匿名类型获取严重事故
1 using (var context = new EFRecipesEntities()) 2 { 3 // 删除之前的测试数据 4 context.Database.ExecuteSqlCommand("delete from chapter3.accident"); 5 context.Database.ExecuteSqlCommand("delete from chapter3.worker"); 6 // 添加新的测试数据 7 var worker1 = new Worker { Name = "John Kearney" }; 8 var worker2 = new Worker { Name = "Nancy Roberts" }; 9 var worker3 = new Worker { Name = "Karla Gibbons" }; 10 context.Accidents.Add(new Accident 11 { 12 Description = "Cuts and contusions", 13 Severity = 3, 14 Worker = worker1 15 }); 16 context.Accidents.Add(new Accident 17 { 18 Description = "Broken foot", 19 Severity = 4, 20 Worker = worker1 21 }); 22 context.Accidents.Add(new Accident 23 { 24 Description = "Fall, no injuries", 25 Severity = 1, 26 Worker = worker2 27 }); 28 context.Accidents.Add(new Accident 29 { 30 Description = "Minor burn", 31 Severity = 3, 32 Worker = worker2 33 }); 34 context.Accidents.Add(new Accident 35 { 36 Description = "Back strain", 37 Severity = 2, 38 Worker = worker3 39 }); 40 context.SaveChanges(); 41 } 42 43 using (var context = new EFRecipesEntities()) 44 { 45 // 显式禁用延迟加载 46 context.Configuration.LazyLoadingEnabled = false; 47 var query = from w in context.Workers 48 select new 49 { 50 Worker = w, 51 Accidents = w.Accidents.Where(a => a.Severity > 2) 52 }; 53 query.ToList(); 54 var workers = query.Select(r => r.Worker); 55 Console.WriteLine("Workers with serious accidents..."); 56 foreach (var worker in workers) 57 { 58 Console.WriteLine("{0} had the following accidents", worker.Name); 59 if (worker.Accidents.Count == 0) 60 Console.WriteLine("\t--None--"); 61 foreach (var accident in worker.Accidents) 62 { 63 Console.WriteLine("\t{0}, severity: {1}", 64 accident.Description, accident.Severity.ToString()); 65 } 66 } 67 } 68 69 Console.WriteLine("\nPress <enter> to continue..."); 70 Console.ReadLine(); 71 }
下面是代码清单3-21的输出:
Workers with serious accidents... John Kearney had the following accidents Cuts and contusions, severity: 3 Broken foot, severity: 4 Nancy Roberts had the following accidents Minor burn, severity: 3 Karla Gibbons had the following accidents --None--
原理
正如你在随后的第五章中看到的那样,我们需要立即加载一个关联集合,我们经常使用Include()方法和一个查询路径。(Include()方法在单个查询中返回父对象和所有的子对象)。然后,Include()方法不允许过滤关联的子实体对象,在本节中,我们展示了,通过轻微的改变,让你可以加载并过滤相关联的子实体对象。
在代码块中,我们创建了一些workers,并分配给它们相关的不同等级的accidents。不得不承认,分配事故给人,有点毛骨悚然!但,这只是为了得到一些可以让我们继续工作的数据。
在随后的查询中,我们获取所有的工人并将结果投射到匿名对象上,这个类型包含了一个worker和一个accidents集合。对于accidents,应该注意,我们是如何过滤集合,只获取严重事故的。
接下来的一行,非常重要!(译注:也就是query.ToList();),我们通过调用ToList()方法,强制查询求值。(记住,LINQ查询默认为延迟加载。意思是,直到结果被使用时,查询才被真正地执行。ToList()方法能强制查询执行)。在Dbcontext(译注:其实是它的子类EFRecipesEntities)中枚举所有的工人和所有的相关的严重事故。 匿名类型不会把accidents附加到workers上,但是通过把它们带到上下文中,实体框架会填充导航属性,将每一个严重事故集合accidents附加到合适的worker上。这个过程一般叫做:Entity Span。这是一个强大而微妙的,发生在实体框架实例化实体类型及它们之间关系的幕后的副作用。
我们关闭了延迟加载,以便让accidents在我们的过滤查询中加载(我们将在第5章讨论延迟加载)。如果打开延迟加载,所有的accidents将在我们引用worker的accidents时才加载。这将导致过虑失败。
我们一旦有了结果集合,就可以枚举并打印出每个worker和它的accidents信息。如果一个worker没有任何严重的accidents,我们打印none来指示他们的安全记录。
实体框架交流QQ群: 458326058,欢迎有兴趣的朋友加入一起交流
谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/VolcanoCloud/