《Entity Framework 6 Recipes》中文翻译系列 (44) ------ 第八章 POCO之POCO中使用值对象和对象变更通知
翻译的初衷以及为什么选择《Entity Framework 6 Recipes》来学习,请看本系列开篇
8-4 POCO中使用值对象(Complex Type--也叫复合类型)属性
问题
你想在POCO中使用值对象。
解决方案
假设你有如图8-5所示的模型。在模型中,属性Name是一个值对象。

图8-5. 一个包含employee的模型,属性Name是一个值对象,它由FirstName和LastName复合而成
POCO支持值对象,当你重构两个或多个实体属性到一个值对象时,一个新的类在默认情况下被生成,这个类就是这个值对象的类型。一个类型为这个值对象类型的属性同时也被添加到主实体中。只有类被支持,因为实体框架在保存值对象时生成了它们。代码清单8-6演示了,使用值对象类型的Name属性来表示员工的姓和名。
代码清单8-6. 在POCO中使用值对象
class Program
{
static void Main(string[] args)
{
RunExample();
} static void RunExample()
{
using (var context = new EFRecipesEntities())
{
context.Employees.Add(new Employee
{
Name = new Name
{
FirstName = "Annie",
LastName = "Oakley"
},
Email = "aoakley@wildwestshow.com"
});
context.Employees.Add(new Employee
{
Name = new Name
{
FirstName = "Bill",
LastName = "Jordan"
},
Email = "BJordan@wildwestshow.com"
});
context.SaveChanges();
} using (var context = new EFRecipesEntities())
{
foreach (var employee in
context.Employees.OrderBy(e => e.Name.LastName))
{
Console.WriteLine("{0}, {1} email: {2}",
employee.Name.LastName,
employee.Name.FirstName,
employee.Email);
}
} Console.WriteLine("Enter input:");
string line = Console.ReadLine();
if (line == "exit")
{
return;
};
}
} public partial class Employee
{
public Employee()
{
this.Name = new Name();
} public int EmployeeId { get; set; }
public string Email { get; set; } public Name Name { get; set; }
} public partial class Name
{
public string FirstName { get; set; }
public string LastName { get; set; }
} public partial class EFRecipesEntities : DbContext
{
public EFRecipesEntities()
: base("name=EFRecipesEntities")
{
} protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
} public DbSet<Employee> Employees { get; set; }
}
代码清单8-6的输出如下:
Jordan, Bill email: BJordan@wildwestshow.com
Oakley, Annie email: aoakley@wildwestshow.com
原理
当你在POCO中使用值对象时,请记住下面两条:
1、值对象必须是一个类;
2、继承不能用于值对象;
在实体框架中,值对象不能使用变化跟踪。对值对象的修改,不能体现在变化跟踪中。这注意着,如果你在一个值对象的属性上将其标记为virtual,也不会获得变化跟踪代理的支持。所有的变化跟踪都是基于快照的。
当你使用值对象删除一个还未从数据库中加载的实体时,你需要创建一个值对象的实例。在实体框架中,值对象的实例是实体的一部分,它不支持null值。代码清单8-7演示了一种处理删除的方法。
代码清单8-7. 删除一个包含值对象的实体
int id = ;
using (var context = new EFRecipesEntities())
{
var emp = context.Employees.Where(e =>
e.Name.FirstName.StartsWith("Bill")).FirstOrDefault();
id = emp.EmployeeId;
} using (var context = new EFRecipesEntities())
{
var empDelete = new Employee
{
EmployeeId = id,
Name = new Name
{
FirstName = string.Empty,
LastName = string.Empty
}
};
context.Employees.Attach(empDelete);
context.Employees.Remove(empDelete);
context.SaveChanges();
} using (var context = new EFRecipesEntities())
{
foreach (var employee in
context.Employees.OrderBy(e => e.Name.LastName))
{
Console.WriteLine("{0}, {1} email: {2}",
employee.Name.LastName,
employee.Name.FirstName,
employee.Email);
}
}
在代码清单8-7中,我们首先查找到Bill Jordan的EmployeeId。因为我们要演示,删除事先没有加载到上下文对象中的Bill,所以,我们创建了一个新的上下文对象,演示通过给定Bill的EmployeeId来删除他。我们需要创建一个Employee实体的实例。这是因为Name属性不能为空,给FirstName和LastName设置了什么值不要紧。我们通过给值对象属性赋值一个Name类型的实例(Dummy)来满足这个要求。当我们调用了方法Attach(),Remove()和SaveChanges()后,就会删除这个实体。
8-5 对象变更通知
问题
你正在使用POCO,在你的对象发生改变时,你想得到实体框架和对象状态管理的通知。
解决方案
假设你有如图8-6所示的模型。

图8-6. 一个包含实体donor和donation的模型
这个模型表示捐款人和他们的捐款。因为有一些捐款是匿名的,所以donor和donation之间的关系是0..1 to *。
我们想修改实体,比如,将一个donation从一个donor移动到另一个donor,同时得到实体框架和对象管理器关于这些变动的通知。另外,我们想实体框架凭借这些通知,修正被变动影响了的关系。 在示例中,如果修改了捐款项对应的捐款人,我们希望实体框架能修正两边的关系。代码清单8-8对此进行了演示。
代码清单8-8的关键部分是,我们将所有的属性都标记为virtual,设置每个集合的类型为ICollection<T>。这样做,主要是允许实体框架为每一个POCO实体创建一个代理,在代理类中实现变化跟踪。当我们创建一个POCO实体的实例时,实体框架会动态地创建一个派生至实体的类,这个类充当实体的代理。这个代理重写了实体中标记为virtual的属性,增加了一些勾子。当属性被访问时,这些勾子会自动地执行。这项技术被用来实现延迟加载和对象变化跟踪。注意实体框架不会为让代理什么也不做的实体生成代理。这句话的意思是,你可以将实体设置为 sealed 或者不包含virtual标记的属性,这样就可以避免代理的生成。
代码清单8-8. 将所有的属性都标记为virtual,设置每个集合的类型为ICollection<T>,以此获取代理类的变化跟踪功能
class Program
{
static void Main(string[] args)
{
RunExample();
} static void RunExample()
{
using (var context = new EFRecipesEntities())
{
var donation = context.Donations.Create();
donation.Amount = 5000M; var donor1 = context.Donors.Create();
donor1.Name = "Jill Rosenberg";
var donor2 = context.Donors.Create();
donor2.Name = "Robert Hewitt"; //把捐款归给jill,并保存
donor1.Donations.Add(donation);
context.Donors.Add(donor1);
context.Donors.Add(donor2);
context.SaveChanges(); // 现在把捐款归给Rebert
donation.Donor = donor2; // 报告
foreach (var donor in context.Donors)
{
Console.WriteLine("{0} has given {1} donation(s)", donor.Name,
donor.Donations.Count().ToString());
}
Console.WriteLine("Original Donor Id: {0}",
context.Entry(donation).OriginalValues["DonorId"]);
Console.WriteLine("Current Donor Id: {0}",
context.Entry(donation).CurrentValues["DonorId"]);
}
}
}
public partial class Donation
{
public int DonationId { get; set; }
public Nullable<int> DonorId { get; set; }
public decimal Amount { get; set; } public virtual Donor Donor { get; set; }
}
public partial class Donor
{
public Donor()
{
this.Donations = new HashSet<Donation>();
} public int DonorId { get; set; }
public string Name { get; set; } public virtual ICollection<Donation> Donations { get; set; }
}
public partial class EFRecipesEntities : DbContext
{
public EFRecipesEntities()
: base("name=EFRecipesEntities")
{
} protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
} public DbSet<Donation> Donations { get; set; }
public DbSet<Donor> Donors { get; set; }
}
代码清单8-8输出如下:
Jill Rosenberg has given donation(s)
Robert Hewitt has given donation(s)
Original Donor Id:
Current Donor Id:
原理
作为默认方式,实体框架使用基于快照的方法来检测POCO实体的变更。如果你在POCO实体中更改一小点代码。实体框架创建的变化跟踪代理都能让上下文保持同步。
变化跟踪给我们带来了两点好处,一个是实体框架得到变更通知,它能保持对象图的状态信息和你的POCO实体同步。意思是说,使用基于快照的方法,不需要花时间来检查变更。
另一点是,当实体框架得到处于关系两边实体中一边的变更通知时,如果有需要,它能反映到关系的另一边。在代码清单8-8中,我们将捐款项从一个捐款人移动到另一个捐款人,实体框架能修正两个捐款人的捐款项集合。
实体框架为POCO实体类生成变化跟踪的代理需要满足如下条件。
1、类必须是Public的,不是abstract类,不是sealed类;
2、需要持久化的属性必须是virtual标记的,且实现了getter和setter;
3、你必须将基于集合的导航属性的类型设为ICollection<T>,它们不能是一个具体的实现类,也不能是另一个派生至ICollection<T>的接口;
一旦你的POCO实体满足这些要求,实体框架就会为你的POCO实体返回一个代理实例。如果需要创建一个实例,你需要像代码清单8-8那样使用DbContext中的Create()方法。这个方法创建一个POCO实体的实例,并且,它会把所有的集合初始化为EntityCollection的实例。把POCO实体的集合作为Entitycollection的实例,这是因为它能修正关系。
实体框架交流QQ群: 458326058,欢迎有兴趣的朋友加入一起交流
谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/VolcanoCloud/
《Entity Framework 6 Recipes》中文翻译系列 (44) ------ 第八章 POCO之POCO中使用值对象和对象变更通知的更多相关文章
- 《Entity Framework 6 Recipes》翻译系列 (1) -----第一章 开始使用实体框架之历史和框架简述
微软的Entity Framework 受到越来越多人的关注和使用,Entity Framework7.0版本也即将发行.虽然已经开源,可遗憾的是,国内没有关于它的书籍,更不用说好书了,可能是因为EF ...
- 《Entity Framework 6 Recipes》翻译系列(2) -----第一章 开始使用实体框架之使用介绍
Visual Studio 我们在Windows平台上开发应用程序使用的工具主要是Visual Studio.这个集成开发环境已经演化了很多年,从一个简单的C++编辑器和编译器到一个高度集成.支持软件 ...
- 《Entity Framework 6 Recipes》翻译系列 (4) -----第二章 实体数据建模基础之从已存在的数据库创建模型
不知道对EF感兴趣的并不多,还是我翻译有问题(如果是,恳请你指正),通过前几篇的反馈,阅读这个系列的人不多.不要这事到最后成了吃不讨好的事就麻烦了,废话就到这里,直奔主题. 2-2 从已存在的数据库创 ...
- 《Entity Framework 6 Recipes》翻译系列 (3) -----第二章 实体数据建模基础之创建一个简单的模型
第二章 实体数据建模基础 很有可能,你才开始探索实体框架,你可能会问“我们怎么开始?”,如果你真是这样的话,那么本章就是一个很好的开始.如果不是,你已经建模,并在实体分裂和继承方面感觉良好,那么你可以 ...
- 《Entity Framework 6 Recipes》翻译系列 (5) -----第二章 实体数据建模基础之有载荷和无载荷的多对多关系建模
2-3 无载荷(with NO Payload)的多对多关系建模 问题 在数据库中,存在通过一张链接表来关联两张表的情况.链接表仅包含连接两张表形成多对多关系的外键,你需要把这两张多对多关系的表导入到 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (42) ------ 第八章 POCO之使用POCO
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 第八章 POCO 对象不应该知道如何保存它们,加载它们或者过滤它们.这是软件开发中熟 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (43) ------ 第八章 POCO之使用POCO加载实体
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 8-2 使用POCO加载关联实体 问题 你想使用POCO预先加载关联实体. 解决方 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (45) ------ 第八章 POCO之获取原始对象与手工同步对象图和变化跟踪器
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 8-6 获取原始对象 问题 你正在使用POCO,想从数据库获取原始对象. 解决方案 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (46) ------ 第八章 POCO之领域对象测试和仓储测试
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 8-8 测试领域对象 问题 你想为领域对象创建单元测试. 这主要用于,测试特定的数 ...
随机推荐
- CommonJS, AMD 和 RequireJS之间的关系(转载)
先说说CommonJS CommonJS - 大家是不是觉得JavaScript仅仅是一个客户端的编译语言,其实JavaScript设计之初不仅仅是针对客户端设计的语言.后来只是由于Web的迅速流行, ...
- Python OS模块常用函数说明
Python的标准库中的os模块包含普遍的操作系统功能.如果你希望你的程序能够与平台无关的话,这个模块是尤为重要的.即它允许一个程序在编写后不需要任何改动,也不会发生任何问题,就可以在Linux和Wi ...
- Tomcat的粗略介绍
因为工作的缘故很多项目启动需要通过Tomcat修改配置文件进行启动项目,所以相应的就了解了下Tomcat便于在以后比我更新的新人面前装逼. 1.bin目录 我们能用到的无非一个启动跟一个关闭没什么好说 ...
- android二维码生成
前生: 一维码:条形码 数字 缺点:不好看,占面积, 好了,请看效果图: 在准备之前我们要导一个包:core-3.2.1.jar 下载请访问: http://download.csdn.net/do ...
- < meta > 元素
< meta > 元素 概要 标签提供关于HTML文档的元数据.元数据不会显示在页面上,但是对于机器是可读的.它可用于浏览器(如何显示内容或重新加载页面),搜索引擎(关键词),或其他 we ...
- [软件推荐、资料分享]MATLAB
MATLAB是美国MathWorks公司出品的商业数学软件,用于算法开发.数据可视化.数据分析以及数值计算的高级技术计算语言和交互式环境,主要包括MATLAB和Simulink两大部分. Matlab ...
- xcode 版本控制推送代码到远程git仓库的步骤
一 代码推送到远程git仓库 1.在git中建立一个下项目(假设项目名称为->Mygit) 2.在xocde上新建一个测试项目(假设项目名称为GitTest) 3.打开电脑终端: 1)首先利用终 ...
- C++ activemq CMS 学习笔记.
很早前就仓促的接触过activemq,但当时太赶时间.后面发现activemq 需要了解的东西实在是太多了. 关于activemq 一直想起一遍文章.但也一直缺少自己的见解.或许是网上这些文章太多了. ...
- 求N个数的最大公约数和最小公倍数(转)
除了分解质因数,还有另一种适用于求几个较小数的最大公约数.最小公倍数的方法 下面是数学证明及算法实现 令[a1,a2,..,an] 表示a1,a2,..,an的最小公倍数,(a1,a2,..,an)表 ...
- ubuntu环境下vmware取消自动启动服务
概述其实vmware这个服务取不取消,影响不大,主要是我有强迫症,在不用虚拟机的时候,看着vmware占着进程真心不爽,想要解决这个问题,在用虚拟机的时候启动服务,反之,则停.接下来,我说一下实现吧. ...