翻譯的初衷以及為什么選擇《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 = 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()后,就會刪除這個實體。
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>,以此獲取代理類的變化跟蹤功能
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實體類生成變化跟蹤的代理需要滿足如下條件。
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/