《Entity Framework 6 Recipes》中文翻译系列 (26) ------ 第五章 加载实体和导航属性之延缓加载关联实体和在别的LINQ查询操作中使用Include()方法
翻译的初衷以及为什么选择《Entity Framework 6 Recipes》来学习,请看本系列开篇
5-7 在别的LINQ查询操作中使用Include()方法
问题
你有一个LINQ查询,使用了类似这样的操作 group by,join,和where;你想使用Include()方法预先加载额外的实体。另外你想使用Code-First来管理数据访问。
解决方案
假设你有如图5-22所示的概念模型
图5-22 一个简单的包含Club和Event以及它们之间一对多关联的模型
在Visual Studio中添加一个名为Recipe7的控制台应用,并确保引用了实体框架6的库,NuGet可以很好的完成这个任务。在Reference目录上右键,并选择 Manage NeGet Packages(管理NeGet包),在Online页,定位并安装实体框架6的包。这样操作后,NeGet将下载,安装和配置实体框架6的库到你的项目中。
创建一个名为Club和Event的类,复制代码清单5-41中的属性到这个类中,创建实体Club和Event实体。
代码清单5-14. Club、Event 实体类
public class Club
{
public Club()
{
Events = new HashSet<Event>();
} public int ClubId { get; set; }
public string Name { get; set; }
public string City { get; set; } public virtual ICollection<Event> Events { get; set; }
} public class Event
{
public int EventId { get; set; }
public string EventName { get; set; }
public DateTime EventDate { get; set; }
public int ClubId { get; set; } public virtual Club Club { get; set; }
}
接下来,创建一个名为Recipe7Context的类,并将代码清单5-51中的代码添加到其中,并确保其派生到DbContext类。
1 public class Recipe7Context : DbContext
2 {
3 public Recipe7Context()
4 : base("Recipe7ConnectionString")
5 {
6 // 禁用实体框架的模型兼容性
7 Database.SetInitializer<Recipe7Context>(null);
8 }
9
10 protected override void OnModelCreating(DbModelBuilder modelBuilder)
11 {
12 modelBuilder.Entity<Club>().ToTable("Chapter5.Club");
13 }
14
15 public DbSet<Club> Clubs { get; set; }
16 }
接下来添加App.Config文件到项目中,并使用代码清单5-6中的代码添加到文件的ConnectionStrings小节下。
<connectionStrings>
<add name="Recipe7ConnectionString"
connectionString="Data Source=.;
Initial Catalog=EFRecipes;
Integrated Security=True;
MultipleActiveResultSets=True"
providerName="System.Data.SqlClient" />
</connectionStrings>
为了与group by从句组合使用Include()方法,Include()方法必须放在针对父实体的过虑和分组操作之后。如代码清单5-17所示。
代码清单5-17 . 当父实体上应用过虑和分组时,Include()方法的正确位置。
using (var context = new Recipe7Context())
{
var club = new Club {Name = "Star City Chess Club", City = "New York"};
club.Events.Add(new Event
{
EventName = "Mid Cities Tournament",
EventDate = DateTime.Parse("1/09/2010"),
Club = club
});
club.Events.Add(new Event
{
EventName = "State Finals Tournament",
EventDate = DateTime.Parse("2/12/2010"),
Club = club
});
club.Events.Add(new Event
{
EventName = "Winter Classic",
EventDate = DateTime.Parse("12/18/2009"),
Club = club
}); context.Clubs.Add(club); context.SaveChanges();
} using (var context = new Recipe7Context())
{
var events = from ev in context.Events
where ev.Club.City == "New York"
group ev by ev.Club
into g
select g.FirstOrDefault(e1 => e1.EventDate == g.Min(evt => evt.EventDate)); var eventWithClub = events.Include("Club").First(); Console.WriteLine("The next New York club event is:");
Console.WriteLine("\tEvent: {0}", eventWithClub.EventName);
Console.WriteLine("\tDate: {0}", eventWithClub.EventDate.ToShortDateString());
Console.WriteLine("\tClub: {0}", eventWithClub.Club.Name);
} Console.WriteLine("Press <enter> to continue...");
Console.ReadLine();
代码清单5-17的输出如下:
The next New York club event is:
Event: Winter Classic
Date: //
Club: Star City Chess Club
原理
我们创建了一个俱乐部(Club)和三个Events(活动)实体对象。在查询中,我们获取New York 的俱乐部中的所有活动。按俱乐部分组,并查找日期中最早的活动。注意,LINQ扩展方法FirstOrDefault(),巧妙地嵌入到了Select投影操作中。然而变量events只是一个表达式,它还没有在数据库中执行任何操作。
接下来,我们凭借Include()方法预先加载关联实体Club对象的信息,我们将第一个LINQ查询变量,events,作为第二个LINQ查询的输入。这是LINQ组合查询的一个示例。将一个复杂的LINQ查询转换成一系列的短小查询。前面的查询变量是后面查询的数据源。
注意,我们使用First()方法只是为获取第一个Event实例,这样将返回一个Event类型,而不是Event对象的集合。实体框架6包含了一个新的名为 IQueryableExtensions的接口,它公布了Include()方法的原型,它能接受一个基于字符串或是强类型的查询路径作为参数。IQueryableExtensions替换了EF4和EF5中的DbExtension类。
很多开发人员觉得Include()方法很让人迷惑,在一些情况下,智能感知不能有效地提示(因为表达式类型)。在一些情况下,它会在运行时悄悄地被忽略掉。特别地,除非编译器无法确定其结果类型,否则不会给出警告或提示。很多问题都在运行时才会暴露出来,这会使问题更难解决。这里有一些使用Include()方法的准则:
1、Include()方法是一个在IQueryable<T>上的扩展方法;
2、Include()方法只能应用在最终的查询结果集上,当它被在subquery(子查询)、join(连接)或者嵌套从句中,当生成命令树时,它将被忽略掉。在幕后,实体框架会把你的LINQ to Entites查询转换成一棵命令树,然后数据库提供者(database provider)将其处理并构建成一个用于数据库的SQL查询(译注:这一点很重要,我在上面吃过亏,直到现在才弄明白)
3、Include()方法只能应用 在实体类型的结果集上,如果表达式将结果投影到一个非实体类型的类型上,Include()方法将被忽略。
4、在Include()方法和最外面的操作之间,不能改变结果集的类型。例如,一个group by从句,改变结果集的类型。
5、用于Include()方法的查询路径表达式必须从最外层操作返回的类型中的导航属性开始,查询路径不能从任意点开始。
让我们看看,代码清单5-17是如何运行这些规则的,这个查询,将活动(events)按赞助的俱乐部分组,group by将结果类型从Event改变成一个分组结果集。第四条规则告诉我们,需要在group by从句改变结果类型之后再调用Include()。我们在结尾处调用Include()方法。 如果我们过早应用Include()方法,像这样 from ev in context.Events.Include,那么,Include()方法将被悄悄地从命令树上移除并不在起用。
5-8 延缓加载(Deferred Loading)相关实体
问题
你有一个实体的实例,你想在一个单独的查询中延缓加载其中两个或多个关联实体。这里尤其重要的是,我们如何使用Load()方法来避免查询相同的对象两次。另外你想使用Code-First来管理数据访问。
解决方案
假设你有如图5-23所示的概念模型.
图5-23 一个包含职员(employee),他的部门(department)和部门所在的公司(company)的模型
在Visual Studio中添加一个名为Recipe8的控制台应用,并确保引用了实体框架6的库,NuGet可以很好的完成这个任务。在Reference目录上右键,并选择 Manage NeGet Packages(管理NeGet包),在Online页,定位并安装实体框架6的包。这样操作后,NeGet将下载,安装和配置实体框架6的库到你的项目中。
接下来我们创建三个实体对象:Company,Departmet和Employee,复制代码清单5-18中的属性到这三个类中。
代码清单5-18. 实体类
public class Company
{
public Company()
{
Departments = new HashSet<Department>();
} public int CompanyId { get; set; }
public string Name { get; set; } public virtual ICollection<Department> Departments { get; set; }
} public class Department
{
public Department()
{
this.Employees = new HashSet<Employee>();
} public int DepartmentId { get; set; }
public string Name { get; set; }
public int CompanyId { get; set; } public virtual Company Company { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
} public class Employee
{
public int EmployeeId { get; set; }
public string Name { get; set; }
public int DepartmentId { get; set; } public virtual Department Department { get; set; }
}
接下来,创建一个名为Recipe8Context的类,并将代码清单5-19中的代码添加到其中,并确保其派生到DbContext类。
public partial class Recipe8Context : DbContext
{
public Recipe8Context()
: base("Recipe8ConnectionString")
{
// 禁用实体框架的模型兼容性
Database.SetInitializer<Recipe8Context>(null);
} protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Company>().ToTable("Chapter5.Company");
modelBuilder.Entity<Employee>().ToTable("Chapter5.Employee");
modelBuilder.Entity<Department>().ToTable("Chapter5.Department");
} public DbSet<Company> Companies { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Employee> Employees { get; set; }
}
接下来添加App.Config文件到项目中,并使用代码清单5-20中的代码添加到文件的ConnectionStrings小节下。
<connectionStrings>
<add name="Recipe8ConnectionString"
connectionString="Data Source=.;
Initial Catalog=EFRecipes;
Integrated Security=True;
MultipleActiveResultSets=True"
providerName="System.Data.SqlClient" />
</connectionStrings>
在图5-23所示的模型中,一个职员(Employee)被关联到一个确切的部门(Department)。每个部门被关联到一个确切公司(Company)。
给定一个Employee的实例,你想加载他的部门以及部门所在的公司。是什么让这个问题变得有点特别呢? 我们已经有了一个Employee的实例,我们想避免再一次到数据库中获取Emplyee对象的副本,这种情况,我们可以使用Include()方法来获取关联实体Company和Department。也许,在真实的情况中,Employee的获取和实例化需要非常高的代价。
我们可以使用Load()方法,两次去加载关联实体,一次加载Department实例,一次去加载Company实例。然而,这会产生两次数据库交互。为了在一个查询中加载关联实体的实例,我们可以使用Include()方法和包含Department,Company查询路径,重新在Emlpoyee实体集上查询。或者组合使用Reference()和Query()方法。代码清单5-21演示了这些方法。
代码清单5-21.将数据插入到模型并使用两种稍有不同的方法加载关联实体
using (var context = new Recipe8Context())
{
var company = new Company {Name = "Acme Products"};
var acc = new Department {Name = "Accounting", Company = company};
var ship = new Department {Name = "Shipping", Company = company};
var emp1 = new Employee {Name = "Jill Carpenter", Department = acc};
var emp2 = new Employee {Name = "Steven Hill", Department = ship};
context.Employees.Add(emp1);
context.Employees.Add(emp2);
context.SaveChanges();
} // 第一种方法
using (var context = new Recipe8Context())
{
// 假设我们已经拥有一个employee
var jill = context.Employees.First(o => o.Name == "Jill Carpenter"); // 获取Jill的部门和公司, 但我们需要重新加载employee
var results = context.Employees.Include("Department.Company")
.First(o => o.EmployeeId == jill.EmployeeId);
Console.WriteLine("{0} works in {1} for {2}", jill.Name, jill.Department.Name,
jill.Department.Company.Name);
} //更有效的方法, 不用再加载employee
using (var context = new Recipe8Context())
{
// 假设我们已经拥有一个employee
var jill = context.Employees.Where(o => o.Name == "Jill Carpenter").First(); //凭借Entry、Reference,Query和Include方法获取Department和Company数据,不用去查询底层的Employee表
context.Entry(jill).Reference(x => x.Department).Query().Include(y => y.Company).Load(); Console.WriteLine("{0} works in {1} for {2}", jill.Name, jill.Department.Name,
jill.Department.Company.Name);
} Console.WriteLine("Press <enter> to continue...");
Console.ReadLine();
代码清单5-21的输出如下:
Jill Carpenter works in Accounting for Acme Products
Jill Carpenter works in Accounting for Acme Products
原理
如果我们还没有Employee实体的实例,我们可以简单地使用Include()方法和一个查询路径 Department.Company来实现 。这是我们这前使用的方法。它的缺点是,它会获取employee实体的所有列。有很多情况下,这是一个昂贵的操作。因为我们已经加载对象到上下文中,再一次去数据库获取所有的列并传输到上下文中,这是一个浪费!
在第二个查询中,我们使用上下文对象DbContext公布的Entry()方法访问Employee对象并对其执行操作。然后我们链式调用Reference()方法和DbReferenceEntity类的Query()方法,返回一个从数据库中加载关联对象Deparment的查询。另外,我们链式调用Include()方法来拉取关联对象Company的信息。正如所期望的那样,这个查询获取了Department和Company的数据,它没有去获取Employees的数据,因为这些数据已经存在于上下文对象中了。
实体框架交流QQ群: 458326058,欢迎有兴趣的朋友加入一起交流
谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/VolcanoCloud/
《Entity Framework 6 Recipes》中文翻译系列 (26) ------ 第五章 加载实体和导航属性之延缓加载关联实体和在别的LINQ查询操作中使用Include()方法的更多相关文章
- 《Entity Framework 6 Recipes》中文翻译系列 (28) ------ 第五章 加载实体和导航属性之测试实体是否加载与显式加载关联实体
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 5-11 测试实体引用或实体集合是否加载 问题 你想测试关联实体或实体集合是否已经 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (22) -----第五章 加载实体和导航属性之延迟加载
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 第五章 加载实体和导航属性 实体框架提供了非常棒的建模环境,它允许开发人员可视化地使 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (23) -----第五章 加载实体和导航属性之预先加载与Find()方法
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 5-2 预先加载关联实体 问题 你想在一次数据交互中加载一个实体和与它相关联实体. ...
- 《Entity Framework 6 Recipes》中文翻译系列 (24) ------ 第五章 加载实体和导航属性之查询内存对象
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 5-4 查询内存对象 问题 你想使用模型中的实体对象,如果他们已经加载到上下文中, ...
- 《Entity Framework 6 Recipes》中文翻译系列 (25) ------ 第五章 加载实体和导航属性之加载完整的对象图和派生类型上的导航属性
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 5-5 加载完整的对象图 问题 你有一个包含许多关联实体的模型,你想在一次查询中, ...
- 《Entity Framework 6 Recipes》中文翻译系列 (27) ------ 第五章 加载实体和导航属性之关联实体过滤、排序、执行聚合操作
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 5-9 关联实体过滤和排序 问题 你有一实体的实例,你想加载应用了过滤和排序的相关 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (29) ------ 第五章 加载实体和导航属性之过滤预先加载的实体集合和修改外键关联
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 5-13 过滤预先加载的实体集合 问题 你想过滤预先加载的实体集合,另外,你想使用 ...
- 《Entity Framework 6 Recipes》翻译系列 (1) -----第一章 开始使用实体框架之历史和框架简述
微软的Entity Framework 受到越来越多人的关注和使用,Entity Framework7.0版本也即将发行.虽然已经开源,可遗憾的是,国内没有关于它的书籍,更不用说好书了,可能是因为EF ...
- 《Entity Framework 6 Recipes》翻译系列 (4) -----第二章 实体数据建模基础之从已存在的数据库创建模型
不知道对EF感兴趣的并不多,还是我翻译有问题(如果是,恳请你指正),通过前几篇的反馈,阅读这个系列的人不多.不要这事到最后成了吃不讨好的事就麻烦了,废话就到这里,直奔主题. 2-2 从已存在的数据库创 ...
随机推荐
- android中如何用代码来关闭打开的相机
场景描述: 比如你再应用中打开了系统相机,然后需要在几分钟后自动关闭这个系统相机(不是手动关闭) 1.在activityA中利用startActivityForResult(intent,reques ...
- 浩瀚技术团队... 安卓智能POS移动PDA开单器 开单器 进销存系统 进销存系统
浩瀚技术团队... 智能POS移动PDA开单器 开单器 进销存系统 进销存系统 点餐 会员管理 会员管理 深度解读 手机APP移动办公到底是什么? 快速打单POS·不仅仅是快那么简单!
- SDOI2009
1226: [SDOI2009]学校食堂Dining Description 小F 的学校在城市的一个偏僻角落,所有学生都只好在学校吃饭.学校有一个食堂,虽然简陋,但食堂大厨总能做出让同学们满意的菜肴 ...
- Linux内核补丁批量自动下载工具
Linux kernel官网cgit工具不支持按变更代码进行补丁搜索,想到个办法就是把补丁都抓下来,这样可以在本地搜索.花了2个小时写了个小工具,话不多说,直接看效果: E:\docs\TOOLS\p ...
- VS2015 RTM与ASP.NET 5 RC1之坑
最近Asp.Net 5的RC1出来了 VS2015的Update1也开始进入RC阶段 嗯,微软尿性,是时候转移到VS2015了 开始踩坑之旅 装好VS2015后,当然是开始折腾ASP.Net 5嘛 建 ...
- bootstrap之伪元素
bootstrap之伪元素 参考地址:http://www.cnblogs.com/keyi/p/5943178.html http://www.runoob.com/css/css-pseudo-e ...
- 最简单的android自定义进度条样式
一.自定义圆形进度条样式 1.在安卓项目drawable目录下新建一个xml文件如下:<?xml version="1.0" encoding="utf-8&quo ...
- 使用nodeJs安装Vue-cli
TIP:win10下安装,使用管理员身份进行,否则会有权限限制. 1,安装完成node,node有自带的npm,可以直接在cmd中,找到nodeJs安装的路径下,进行命令行全局安装vue-cli.(n ...
- JAligner的一个坑
JAligner是一个集成多个罚分矩阵的蛋白质序列比对工具包,提供充足的API供开发人员调用. 但是,不可否认的是,它的结构写得不够规范.以前我是将它放在普通的Java项目里使用,没有问题.但是,今天 ...
- iOS 添加中文支持的操作
1.选择工程菜单,这里要选中Project,而不是Targets 2.点击Info菜单, 下拉到最后,看到Localizations. 点击+号. 3.选择中文 chinese-simplif ...