《Entity Framework 6 Recipes》中文翻译系列 (28) ------ 第五章 加载实体和导航属性之测试实体是否加载与显式加载关联实体
翻译的初衷以及为什么选择《Entity Framework 6 Recipes》来学习,请看本系列开篇
5-11 测试实体引用或实体集合是否加载
问题
你想测试关联实体或实体集合是否已经加载到上下文中,另外你想使用Code-First来管理数据访问。
解决方案
假设你有如图5-26所示的概念模型
图5-26 一个包含projects,managers和contractors的模型
在Visual Studio中添加一个名为Recipe11的控制台应用,并确保引用了实体框架6的库,NuGet可以很好的完成这个任务。在Reference目录上右键,并选择 Manage NeGet Packages(管理NeGet包),在Online页,定位并安装实体框架6的包。这样操作后,NeGet将下载,安装和配置实体框架6的库到你的项目中。
接下来我们创建三个实体对象:Contractor,Manager,Project,复制代码清单5-27中的属性到这三个类中。
代码清单5-27. 实体类
- public class Contractor
- {
- public int ContracterID { get; set; }
- public string Name { get; set; }
- public int ProjectID { get; set; }
- public virtual Project Project { get; set; }
- }
- public class Manager
- {
- public Manager()
- {
- Projects = new HashSet<Project>();
- }
- public int ManagerID { get; set; }
- public string Name { get; set; }
- public virtual ICollection<Project> Projects { get; set; }
- }
- public class Project
- {
- public Project()
- {
- Contractors = new HashSet<Contractor>();
- }
- public int ProjectID { get; set; }
- public string Name { get; set; }
- public int ManagerID { get; set; }
- public virtual ICollection<Contractor> Contractors { get; set; }
- public virtual Manager Manager { get; set; }
- }
接下来,创建一个名为Recipe11Context的类,并将代码清单5-24中的代码添加到其中,并确保其派生到DbContext类。
代码清单5-28. 上下文
- public class Recipe11Context : DbContext
- {
- public Recipe11Context()
- : base("Recipe11ConnectionString")
- {
- //禁用实体框架的模型兼容
- Database.SetInitializer<Recipe11Context>(null);
- }
- public DbSet<Contractor> Contractors { get; set; }
- public DbSet<Manager> Managers { get; set; }
- public DbSet<Project> Projects { get; set; }
- protected override void OnModelCreating(DbModelBuilder modelBuilder)
- {
- modelBuilder.Entity<Contractor>().ToTable("Chapter5.Contractor");
- modelBuilder.Entity<Manager>().ToTable("Chapter5.Manager");
- modelBuilder.Entity<Project>().ToTable("Chapter5.Project");
- // 显示映射实体键
- modelBuilder.Entity<Contractor>().HasKey(x => x.ContracterID);
- }
- }
接下来添加App.Config文件到项目中,并使用代码清单5-25中的代码添加到文件的ConnectionStrings小节下。
代码清单5-29. 连接字符串

- <connectionStrings>
- <add name="Recipe11ConnectionString"
- connectionString="Data Source=.;
- Initial Catalog=EFRecipes;
- Integrated Security=True;
- MultipleActiveResultSets=True"
- providerName="System.Data.SqlClient" />
- </connectionStrings>

实体框架公布了一个IsLoaded属性。只要它100%的确定,指定的实体或实体集合全部已经加载且在上下文中有效时,它的值便为True。图5-26中的模型表示,项目(Project),项目的管理者(manager)以及项目的承包商(Contractors)。按代码清单5-30所示,测试关联实体是否加载到上下文对象中。
代码清单5-30.使用IsLoaded判断一个实体或实体集合是否已加载到上下文中
- using (var context = new Recipe11Context())
- {
- var man1 = new Manager {Name = "Jill Stevens"};
- var proj = new Project {Name = "City Riverfront Park", Manager = man1};
- var con1 = new Contractor {Name = "Robert Alvert", Project = proj};
- var con2 = new Contractor {Name = "Alan Jones", Project = proj};
- var con3 = new Contractor {Name = "Nancy Roberts", Project = proj};
- context.Projects.Add(proj);
- context.SaveChanges();
- }
- using (var context = new Recipe11Context())
- {
- var project = context.Projects.Include("Manager").First();
- if (context.Entry(project).Reference(x => x.Manager).IsLoaded)
- Console.WriteLine("Manager entity is loaded.");
- else
- Console.WriteLine("Manager entity is NOT loaded.");
- if (context.Entry(project).Collection(x => x.Contractors).IsLoaded)
- Console.WriteLine("Contractors are loaded.");
- else
- Console.WriteLine("Contractors are NOT loaded.");
- Console.WriteLine("Calling project.Contractors.Load()...");
- context.Entry(project).Collection(x => x.Contractors).Load();
- if (context.Entry(project).Collection(x => x.Contractors).IsLoaded)
- Console.WriteLine("Contractors are now loaded.");
- else
- Console.WriteLine("Contractors failed to load.");
- }
- Console.WriteLine("Press <enter> to continue...");
- Console.ReadLine();
代码清单5-30的输出如下:
- Manager entity is loaded.
- Contractors are NOT loaded.
- Calling project.Contractors.Load()...
- Contractors are now loaded.
原理
我们使用Include()方法,从数据库中预先加载Project实体和与它关联的Manager。
查询之后,我们通过reference()方法获取关联实体Manager的引用和检查IsLoaded属性的值,来判断manager实例是否加载。因为这是一个实体引用(引用一个单独的父实体),调用Entry()方法返回DbEntityEntry类型上的Reference方法,它返回类型上IsLoaded属性有效。因为我已经加载了Projects和Manager,所以,该属性返回True.
接下来我们检查Contractor实体集合是否加载。因为我们没有在Include()方法中预先加载它,也没有使用Load()方法直接加载过它,所以它没有被加载。只要一加载它。IsLoaded属性便会被设置为True.
默认行为,延迟加载,开启时,当关联实体或实体集合被引用时,IsLoaded属性便会被设置为True。延迟加载会让实体框架在实体或实体集合被引用时,自动加载。显式加载和延迟加载有些类似,只是它不是自动的。开发人员必须显式地使用Load()方法来加载实体,它让开发人员可以完全自己控制是否加载,何时加载关联实体。
IsLoaded确切的含义,比看起来更让人迷惑。IsLoaded被调用Load()方法的查询设置,也被隐式的关系跨度设置。当你查询一个实体时,会隐式查询关联实体Key。如果这个隐式查询的结果是一个null值,IsLoaded会被设置成True,意思是数据库没有关联的实体。当我们显示加载关系并发现没有关联实体时,IsLoaded同样会被设置成True. (译注:这里可能会有点难理解,因为涉及到了一个术语:关系跨度(Relationship Span)的理解,它是指EF加载实体时总是一起返回外键值,以此来避免一些列的问题)
5-12 显示加载关联实体
问题
你想直接加载关联实体,不依赖默认的延迟加载功能。
解决方案
假设你有如图5-27所示的概念模型
图5-27 一个包含实体 doctor、appointment、patient的模型
图5-27描述的模型,表示医生(doctor)和他的患者(patient),以及预约(appointment)。代码清单5-31,显示加载关联实体。
代码清单5-31. 使用Load()方法
- using (var context = new EFRecipesEntities())
- {
- var doc1 = new Doctor { Name = "Joan Meyers" };
- var doc2 = new Doctor { Name = "Steven Mills" };
- var pat1 = new Patient { Name = "Bill Rivers" };
- var pat2 = new Patient { Name = "Susan Stevenson" };
- var pat3 = new Patient { Name = "Roland Marcy" };
- var app1 = new Appointment
- {
- Date = DateTime.Today,
- Doctor = doc1,
- Fee = 109.92M,
- Patient = pat1,
- Reason = "Checkup"
- };
- var app2 = new Appointment
- {
- Date = DateTime.Today,
- Doctor = doc2,
- Fee = 129.87M,
- Patient = pat2,
- Reason = "Arm Pain"
- };
- var app3 = new Appointment
- {
- Date = DateTime.Today,
- Doctor = doc1,
- Fee = 99.23M,
- Patient = pat3,
- Reason = "Back Pain"
- };
- context.Appointments.Add(app1);
- context.Appointments.Add(app2);
- context.Appointments.Add(app3);
- context.SaveChanges();
- }
- using (var context = new EFRecipesEntities())
- {
- // 禁用延迟加载,因为我们要显式加载子实体
- context.Configuration.LazyLoadingEnabled = false;
- var doctorJoan = context.Doctors.First(o => o.Name == "Joan Meyers");
- if (!context.Entry(doctorJoan).Collection(x => x.Appointments).IsLoaded)
- {
- context.Entry(doctorJoan).Collection(x => x.Appointments).Load();
- Console.WriteLine("Dr. {0}'s appointments were explicitly loaded.",
- doctorJoan.Name);
- }
- Console.WriteLine("Dr. {0} has {1} appointment(s).",
- doctorJoan.Name,
- doctorJoan.Appointments.Count());
- foreach (var appointment in context.Appointments)
- {
- if (!context.Entry(appointment).Reference(x => x.Doctor).IsLoaded)
- {
- context.Entry(appointment).Reference(x => x.Doctor).Load();
- Console.WriteLine("Dr. {0} was explicitly loaded.",
- appointment.Doctor.Name);
- }
- else
- Console.WriteLine("Dr. {0} was already loaded.",
- appointment.Doctor.Name);
- }
- Console.WriteLine("There are {0} appointments for Dr. {1}",
- doctorJoan.Appointments.Count(),
- doctorJoan.Name);
- doctorJoan.Appointments.Clear();
- Console.WriteLine("Collection clear()'ed");
- Console.WriteLine("There are now {0} appointments for Dr. {1}",
- doctorJoan.Appointments.Count(),
- doctorJoan.Name);
- context.Entry(doctorJoan).Collection(x => x.Appointments).Load();
- Console.WriteLine("Collection loaded()'ed");
- Console.WriteLine("There are now {0} appointments for Dr. {1}",
- doctorJoan.Appointments.Count().ToString(),
- doctorJoan.Name);
- //目前,DbContext 没有API去刷新实体,但底层的ObjectContext有,执行下面的动作。
- var objectContext = ((IObjectContextAdapter)context).ObjectContext;
- var objectSet = objectContext.CreateObjectSet<Appointment>();
- objectSet.MergeOption = MergeOption.OverwriteChanges;
- objectSet.Load();
- Console.WriteLine("Collection loaded()'ed with MergeOption.OverwriteChanges");
- Console.WriteLine("There are now {0} appointments for Dr. {1}",
- doctorJoan.Appointments.Count(),
- doctorJoan.Name);
- }
- //演示先加载部分实体集合,然后再加载剩下的
- using (var context = new EFRecipesEntities())
- {
- // 禁用延迟加载,因为我们要显式加载子实体
- context.Configuration.LazyLoadingEnabled = false;
- //加载第一个doctor然后只附加一个appointment
- var doctorJoan = context.Doctors.First(o => o.Name == "Joan Meyers");
- context.Entry(doctorJoan).Collection(x => x.Appointments).Query().Take().Load();
- //注意,这里IsLoaded返回False,因为所有的实体还没有被加载到上下文
- var appointmentsLoaded = context.Entry(doctorJoan).Collection(x => x.Appointments).IsLoaded;
- Console.WriteLine("Dr. {0} has {1} appointments loaded.",
- doctorJoan.Name,
- doctorJoan.Appointments.Count());
- //当我需要加载剩下的appointments,只需要简单的调用Load()来加载它们
- context.Entry(doctorJoan).Collection(x => x.Appointments).Load();
- Console.WriteLine("Dr. {0} has {1} appointments loaded.",
- doctorJoan.Name,
- doctorJoan.Appointments.Count());
- }
- Console.WriteLine("Press <enter> to continue...");
- Console.ReadLine();
代码清单5-31的输出如下:
- Dr. Joan Meyers's appointments were explicitly loaded.
- Dr. Joan Meyers has appointment(s).
- Dr. Joan Meyers was already loaded.
- Dr. Steven Mills was explicitly loaded.
- Dr. Joan Meyers was already loaded.
- There are appointments for Dr. Joan Meyers
- Collection clear()'ed
- There are now appointments for Dr. Joan Meyers
- Collection loaded()'ed
- There are now appointments for Dr. Joan Meyers
- Collection loaded()'ed with MergeOption.OverwriteChanges
- There are now appointments for Dr. Joan Meyers
- Dr. Joan Meyers has appointments loaded.
- Dr. Joan Meyers has appointments loaded.
- Press <enter> to continue...
译注:书的结果有误,这是我(付灿)运行示例后的输出。
原理
插入一些简单的数据到数据库之后,我们显式地禁用了延迟加载特征,因为我们要显式控制关联实体的加载。有两种方式禁用延迟加载:
1、设置Context.Configuration对象的LazyLoadingEnabled属性为False。它会禁用上下文中所有实体对象的延迟加载。
2、在每个实体类中移除导航属性的virtual修饰关键字。这种方法会禁用相应实体的延迟加载,这样就能让你显式控制延迟加载。
我们先获取一个Doctor实体。如果你使用了显式加载,这将是使用IsLoaded属性检查关联实体或实体集合是否加载的一个好实践。在代码中,我们检查doctor对象的appointments是否加载。如果没有,我们使用Load()方法加载它们。
在foreach循环中,我们枚举了appointments,检查与它关联的doctor是否加载。注意输出,这时只有一个医生被加载,别的没有被加载。这是因为我们的第一个查询只获取了一个doctor。在获取appointmetns的过程中,实体框架会连接医生(doctor)和他的预约(appintments),这个过程被称为(非正式的)Relationship fixup(译注:这些概念虽然已经产生很多年,但中文资料关于它的介绍几乎没有,只看到一位兄弟把它翻译为“关系建立”,个人觉得它能表达这个词的含义,就借用了)。Relationship fixup 不会建立好所有的关联,特别是多对关联的实体。
我们打印出doctor关联实体集合appointments已加载的数量。然后我们调用Clear()方法,清空doctorJoan关联实体集合。这个方法会清除掉doctorJoan和appointments间的关系。有趣的是,它并不会把实例从内存中移除;这些实例仍在上下文中--它们只是不在跟Doctor实体连接。
令人奇怪的是,调用Load()方法重新加载appointemts后,从输出我们看到,doctorJoan的关联集合没有对象。发生了什么呢?原来是因为Load()方法需要使用一个控制如何加载实例进入上下文的参数。该参数的默认值是MergeOption.AppendOnly,它只是简单地把不存在上下文中的实体对象加载到上下文中。在我们示例中,没有没有真正地把实体对象从上下文中移除。在使用Clear()方法时,只是将实体对象从关联集合中移除,而没有从上下文中移除。当我们使用Load()方法重新加载时,由于使用了默认的MergeOption.AppendOnly选项,又没有发现新的实例,所有没有实体对象被加载到上下文中(译注:关联实体集合自然也不会添加,但注意这里的Load()方法是生成了SQL查询语句,产生了数据库交互,并从数据库获取了相应的数据的)。其它的合并选项包含:NoTracking,OverwriteChanges,和PreserveChages。当我们使用OverwriteChanges选项时,appointments出现在Doctor实体对象的关联集合Appointments中了。
注意,我们在代码中是如何进入底层,通过ObjectContext上下文对象,访问实体框架中MergeOption行为的。MergeOption在DbContext中不被直接支持。你可能会回想起,我们使用实体框架时,有两个上下文对象可以使用。在实体框架6中,首选是使用DbContext。它提供了直观,易于使用的,遗留ObectContext上下文对象的外观模式。如代码中所示,可以通过显式转换,仍然可以使用ObjectContext。
与AppendOnly一起,MegeOption类型公布了另外三个选项:
1、NoTracking选项会关闭加载实例的对象状态跟踪。使用NoTracking选项,实体框架将不再跟踪对象的变化,同时也不再知道对象是否已经加载到上下文中。如果对象使用NoTracking选项加载,那么它可以被用于对象的导航属性上。NoTracking有一个额外的副作用。如果我们使用NoTracking选项加载一个Doctor实体,那么,使用Load()方法加载appointments时,不管默认行为AppendOnly,仍然会使用NoTracking。
2、OverwriteChanges选项会使用从数据库获取的数据更新当前对象的值,实体框架会继续使用同一个实体对象。这个选项在你想放弃上下文中对实体对象的修改,并使用数据库中的数据来刷新它时特别管用。这个选项非常有用,例如,你的应用正在实现一个撤消操作的功能。
3、PreserveChanges选项,本质上是OverwriteChanges选项的对立选项。当数据库中有改变时,它会更新实体对象的值。但是当内存里的值发生改变时,它不会更新实体对象的值。一个实体对象在内存中被修改,它不会被刷新。更准确地说,在内存中修改实体对象时,它的当前值(cruuent value)不会改变,但是,如果数据库有改变时,它的初始值(original value)会被更新。
当你使用Load()方法时,这里有一些限制。实体状态为Added,Deleted,或者是Detached时,不能调用Load()方法。
无论在什么时候,只要想限制关联实体集合中实体的加载数量,Load()方法对性能的提升都有帮助。例如,我们的医生有大量的预约,但是在很多时候,他只能处理一部分。在极罕见的情况下会处理整个集合,我可以简单的调用Load()方法加载剩下的appointments实例。如代码清单5-32所示。
代码清单5-32.演示加载部分关联实体集合
- //演示先加载部分实体集合,然后再加载剩下的
- using (var context = new EFRecipesEntities())
- {
- // 禁用延迟加载,因为我们要显式加载子实体
- context.Configuration.LazyLoadingEnabled = false;
- //加载第一个doctor然后只附加一个appointment
- var doctorJoan = context.Doctors.First(o => o.Name == "Joan Meyers");
- context.Entry(doctorJoan).Collection(x => x.Appointments).Query().Take().Load();
- //注意,这里IsLoaded返回False,因为所有的实体还没有被加载到上下文
- var appointmentsLoaded = context.Entry(doctorJoan).Collection(x => x.Appointments).IsLoaded;
- Console.WriteLine("Dr. {0} has {1} appointments loaded.",
- doctorJoan.Name,
- doctorJoan.Appointments.Count());
- //当我需要加载剩下的appointments,只需要简单的调用Load()来加载它们
- context.Entry(doctorJoan).Collection(x => x.Appointments).Load();
- Console.WriteLine("Dr. {0} has {1} appointments loaded.",
- doctorJoan.Name,
- doctorJoan.Appointments.Count());
- }
代码清单5-31的输出如下:
- Dr. Joan Meyers has appointments loaded.
- Dr. Joan Meyers has appointments loaded.
实体框架交流QQ群: 458326058,欢迎有兴趣的朋友加入一起交流
谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/VolcanoCloud/
《Entity Framework 6 Recipes》中文翻译系列 (28) ------ 第五章 加载实体和导航属性之测试实体是否加载与显式加载关联实体的更多相关文章
- 《Entity Framework 6 Recipes》中文翻译系列 (27) ------ 第五章 加载实体和导航属性之关联实体过滤、排序、执行聚合操作
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 5-9 关联实体过滤和排序 问题 你有一实体的实例,你想加载应用了过滤和排序的相关 ...
- 《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》中文翻译系列 (26) ------ 第五章 加载实体和导航属性之延缓加载关联实体和在别的LINQ查询操作中使用Include()方法
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 5-7 在别的LINQ查询操作中使用Include()方法 问题 你有一个LINQ ...
- 《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 从已存在的数据库创 ...
随机推荐
- Beginning Scala study note(8) Scala Type System
1. Unified Type System Scala has a unified type system, enclosed by the type Any at the top of the h ...
- 什么是Angularjs
AngularJs(后面就简称ng了)是一个用于设计动态web应用的结构框架.首先,它是一个框架,不是类库,是像EXT一样提供一整套方案用于设计web应用.它不仅仅是一个javascript框架,因为 ...
- Codeforces Round #388 (Div. 2) - B
题目链接:http://codeforces.com/contest/749/problem/B 题意:给定平行四边形的3个点,输出所有可能的第四个点. 思路:枚举任意两个点形成的直线,然后利用这两个 ...
- SPOJ DISUBSTR ——后缀数组
[题目分析] 后缀数组模板题. 由于height数组存在RMQ的性质. 那么对于一个后缀,与前面相同的串总共有h[i]+sa[i]个.然后求和即可. [代码](模板来自Claris,这个板子太漂亮了) ...
- CGrowableArray解析 _ DXUT容器
CGrowableArray的声明 in DXUTmisc.h //--------------------------- ...
- SOAPUI使用教程-MockService脚本概述
虽然静态MockOperation和MockResponse模型可以给你一定的灵活性,更多的动态行为添加到您的服务让您更模拟复杂的服务功能是很有用的.例如,你可能想: 从请求到响应传输值,修改它们的值 ...
- TDD测试驱动开发
TDD测试驱动开发 一.概念 TDD故名思意就是用测试的方法驱动开发,简单说就是先写测试代码,再写开发代码.传统的方式是先写代码,再测试,它的开发方式与之正好相反. TDD是极限编程的一个最重要的设计 ...
- RESTFUL Architecture
Just review some articles about RESTFUL stuff, my understanding is RESTFUL is another more general v ...
- 怎么将java web 项目导入idea 中
1.将 java web 项目导 入idea 中, 显示 然后进行 Configure 配置. 2. 点击 open module settings. 3. 4. 选择jar包. 5. 6. 配置to ...
- dell笔记本三个系统,ubuntu16.04更新,boot分区容量不足解决办法
本人自己dell物理机上安装windows 7 .centos 1704 和ubuntu1604 三个系统的,分区当时没有使用lVM,boot单独挂/dev/sda7 分区,只有200M,随着2次li ...