翻譯的初衷以及爲何選擇《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/