翻译的初衷以及为何选择《Entity Framework 6 Recipes》来学习,请看本系列开篇html
问题数据库
你想在POCO中使用值对象。框架
解决方案ide
假设你有如图8-5所示的模型。在模型中,属性Name是一个值对象。学习
图8-5. 一个包含employee的模型,属性Name是一个值对象,它由FirstName和LastName复合而成ui
POCO支持值对象,当你重构两个或多个实体属性到一个值对象时,一个新的类在默认状况下被生成,这个类就是这个值对象的类型。一个类型为这个值对象类型的属性同时也被添加到主实体中。只有类被支持,由于实体框架在保存值对象时生成了它们。代码清单8-6演示了,使用值对象类型的Name属性来表示员工的姓和名。this
代码清单8-6. 在POCO中使用值对象spa
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中使用值对象时,请记住下面两条:
一、值对象必须是一个类;
二、继承不能用于值对象;
在实体框架中,值对象不能使用变化跟踪。对值对象的修改,不能体如今变化跟踪中。这注意着,若是你在一个值对象的属性上将其标记为virtual,也不会得到变化跟踪代理的支持。全部的变化跟踪都是基于快照的。
当你使用值对象删除一个还未从数据库中加载的实体时,你须要建立一个值对象的实例。在实体框架中,值对象的实例是实体的一部分,它不支持null值。代码清单8-7演示了一种处理删除的方法。
代码清单8-7. 删除一个包含值对象的实体
int id = 0; 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()后,就会删除这个实体。
问题
你正在使用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>,以此获取代理类的变化跟踪功能
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 RunExample(); 6 } 7 8 static void RunExample() 9 { 10 using (var context = new EFRecipesEntities()) 11 { 12 var donation = context.Donations.Create(); 13 donation.Amount = 5000M; 14 15 var donor1 = context.Donors.Create(); 16 donor1.Name = "Jill Rosenberg"; 17 var donor2 = context.Donors.Create(); 18 donor2.Name = "Robert Hewitt"; 19 20 //把捐款归给jill,并保存 21 donor1.Donations.Add(donation); 22 context.Donors.Add(donor1); 23 context.Donors.Add(donor2); 24 context.SaveChanges(); 25 26 // 如今把捐款归给Rebert 27 donation.Donor = donor2; 28 29 // 报告 30 foreach (var donor in context.Donors) 31 { 32 Console.WriteLine("{0} has given {1} donation(s)", donor.Name, 33 donor.Donations.Count().ToString()); 34 } 35 Console.WriteLine("Original Donor Id: {0}", 36 context.Entry(donation).OriginalValues["DonorId"]); 37 Console.WriteLine("Current Donor Id: {0}", 38 context.Entry(donation).CurrentValues["DonorId"]); 39 } 40 } 41 } 42 public partial class Donation 43 { 44 public int DonationId { get; set; } 45 public Nullable<int> DonorId { get; set; } 46 public decimal Amount { get; set; } 47 48 public virtual Donor Donor { get; set; } 49 } 50 public partial class Donor 51 { 52 public Donor() 53 { 54 this.Donations = new HashSet<Donation>(); 55 } 56 57 public int DonorId { get; set; } 58 public string Name { get; set; } 59 60 public virtual ICollection<Donation> Donations { get; set; } 61 } 62 public partial class EFRecipesEntities : DbContext 63 { 64 public EFRecipesEntities() 65 : base("name=EFRecipesEntities") 66 { 67 } 68 69 protected override void OnModelCreating(DbModelBuilder modelBuilder) 70 { 71 throw new UnintentionalCodeFirstException(); 72 } 73 74 public DbSet<Donation> Donations { get; set; } 75 public DbSet<Donor> Donors { get; set; } 76 }
代码清单8-8输出以下:
Jill Rosenberg has given 0 donation(s) Robert Hewitt has given 1 donation(s) Original Donor Id: 1 Current Donor Id: 2
原理
做为默认方式,实体框架使用基于快照的方法来检测POCO实体的变动。若是你在POCO实体中更改一小点代码。实体框架建立的变化跟踪代理都能让上下文保持同步。
变化跟踪给咱们带来了两点好处,一个是实体框架获得变动通知,它能保持对象图的状态信息和你的POCO实体同步。意思是说,使用基于快照的方法,不须要花时间来检查变动。
另外一点是,当实体框架获得处于关系两边实体中一边的变动通知时,若是有须要,它能反映到关系的另外一边。在代码清单8-8中,咱们将捐款项从一个捐款人移动到另外一个捐款人,实体框架能修正两个捐款人的捐款项集合。
实体框架为POCO实体类生成变化跟踪的代理须要知足以下条件。
一、类必须是Public的,不是abstract类,不是sealed类;
二、须要持久化的属性必须是virtual标记的,且实现了getter和setter;
三、你必须将基于集合的导航属性的类型设为ICollection<T>,它们不能是一个具体的实现类,也不能是另外一个派生至ICollection<T>的接口;
一旦你的POCO实体知足这些要求,实体框架就会为你的POCO实体返回一个代理实例。若是须要建立一个实例,你须要像代码清单8-8那样使用DbContext中的Create()方法。这个方法建立一个POCO实体的实例,而且,它会把全部的集合初始化为EntityCollection的实例。把POCO实体的集合做为Entitycollection的实例,这是由于它能修正关系。
实体框架交流QQ群: 458326058,欢迎有兴趣的朋友加入一块儿交流
谢谢你们的持续关注,个人博客地址:http://www.cnblogs.com/VolcanoCloud/