翻譯的初衷以及為什么選擇《Entity Framework 6 Recipes》來學習,請看本系列開篇
2-10 Table per Hierarchy Inheritance 建模
問題
你有這樣一張數據庫表,有一類型或鑒別列。它能判斷行中的數據在你的應用中代表的是什么。你想使用table per hierarchy(TPH)繼承映射建模。
解決方案
讓我們假設你有如圖2-20中的表(譯注:總感覺作者使用的圖,跟實際描述對不上,比如下圖應該是實體模型圖),Employee表包含hourly employees 和salaried employees的行。列EmployeeType作為鑒別列,鑒別這兩種員工類型的行。 當EmployeType為1時,這一行代表一個專職員工(salaried or full-time employee),當值為2時,這一行代碼一個鍾點工(hourly employee).
圖2-20 一個包含hourly employees 和salaried employees的表 Employee
按下面的步驟,使用TPH基於表Employee建模:
1、在你的項目中創建一個繼承自DbContext的上下文對象EF6RecipesContext;
2、使用代碼清單2-21創建一個抽象的POCO實體Employee;
代碼清單2-21.創建一個抽象的POCO實體Employee
1 [Table("Employee", Schema = "Chapter2")] 2 public abstract class Employee { 3 [Key] 4 [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 5 public int EmployeeId { get; protected set; } 6 public string FirstName { get; set; } 7 public string LastName { get; set; } 8 }
3、使用代碼清單2-22創建一個繼承至Emplyee的POCO實體類FullTimeEmployee.
代碼清單2-22. 創建一個POCO實體類FullTimeEmployee
1 public class FullTimeEmployee : Employee 2 { 3 public decimal? Salary { get; set; } 4 }
4、使用代碼清單2-23創建一個繼承至Emplyee的POCO實體類HourlyEmployee.
代碼清單2-23. 創建一個POCO實體類HourlyEmployee
1 public class HourlyEmployee : Employee { 2 public decimal? Wage { get; set; } 3 }
5、在上下文中添加一個類型為DbSet<Employee>的屬性。
6、在上下文中重寫方法OnModelCreating,在方法中映射你的具體的employee類型到EmployeeType鑒別列。如代碼清單2-24所示.
代碼清單2-24. 重寫上下文中的OnModelCreating方法
protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity<Employee>() .Map<FullTimeEmployee>(m => m.Requires("EmployeeType").HasValue(1)) .Map<HourlyEmployee>(m => m.Requires("EmployeeType").HasValue(2)); }
注意:非共享屬性(例如:Salary和Wage)必須為可空類型。
原理
在table per hierarchy(通常縮寫為TPH)繼承映射中,用一張單獨的表代表整個繼承層次。不像TPT,TPH同時把基類和派生類混合進同一張表的行。它們依據鑒別列來區分。在我們示例中,鑒別列是EmployeeType。
在TPH中,我們設置實體配置中的映射條件,用來指明鑒別列的值讓表映射到不同的派生類型。我們讓基類為抽象類型。通過設置其為抽象類型,我就不用提供一個映射條件,因為一個抽象的實體是不會被創建的。我們永遠不會有一個Employee實體的實例。我們不用在Employee實體中實現一個EmployeeType屬性。列不會用來作映射條件,一般是映射到一個屬性。
代碼清單2-25演示從模型插入和獲取數據。
代碼清單2-25 在我們的TPH模型中插入和獲取數據
1 using (var context = new EF6RecipesContext()) { 2 var fte = new FullTimeEmployee { 3 FirstName = "Jane", 4 LastName = "Doe", 5 Salary = 71500M 6 }; 7 context.Employees.Add(fte); 8 fte = new FullTimeEmployee { 9 FirstName = "John", 10 LastName = "Smith", 11 Salary = 62500M 12 }; 13 context.Employees.Add(fte); 14 var hourly = new HourlyEmployee { 15 FirstName = "Tom", 16 LastName = "Jones", 17 Wage = 8.75M 18 }; 19 context.Employees.Add(hourly); 20 context.SaveChanges(); 21 } 22 using (var context = new EF6RecipesContext()) { 23 Console.WriteLine("--- All Employees ---"); 24 foreach (var emp in context.Employees) { 25 bool fullTime = emp is HourlyEmployee ? false : true; 26 Console.WriteLine("{0} {1} ({2})", emp.FirstName, emp.LastName, 27 fullTime ? "Full Time" : "Hourly"); 28 } 29 Console.WriteLine("--- Full Time ---"); 30 foreach (var fte in context.Employees.OfType<FullTimeEmployee>()) { 31 Console.WriteLine("{0} {1}", fte.FirstName, fte.LastName); 32 } 33 Console.WriteLine("--- Hourly ---"); 34 foreach (var hourly in context.Employees.OfType<HourlyEmployee>()) { 35 Console.WriteLine("{0} {1}", hourly.FirstName, hourly.LastName); 36 } 37 }
代碼清單的輸出為:
--- All Employees ---Jane Doe (Full Time) John Smith (Full Time) Tom Jones (Hourly) --- Full Time ---Jane Doe John Smith --- Hourly ---Tom Jones
代碼清單2-15,創建、初始化、添加兩個full-time employees和一個hourly employee.在查詢中,我們獲取所有的employees,用is操作符來判斷employee是我們擁有員工類型中的哪一種。當我打印出員工姓名時,我們指出他的類型。
在代碼塊中,我們使用泛型方法OfType<T>()獲取full-time employees和hourly employees.
最佳實踐
在TPH繼承映射中,什么時候使用抽象基類,什么時候在實體中創建一個映射條件,存在着爭論。使用一個具體的基類的難點在,查詢出整個繼承中的實例,非常的繁瑣。 這里的最佳實踐是,如果你的應用中不需要基類實體的實例,那么讓它成為抽象類型。
如果你的應用中需要一個基類的實例,可以考慮引進一個新的繼承實體來覆蓋基類中的映射條件屬性。例如,在上例中我們可以創建一個這樣的派生類UnclassifiedEmployee。 一旦有這個派生類后,我們就可以放心地把基類設為抽象類型。這就提供了一種簡單的方式來規避通過在基類中使用映射條件屬性來查詢的問題。
在使用TPH時,有幾條准則需要記住。第一點,映射條件屬性值必須相互獨立。換句話來說,就是你不能將一行,條件映射到兩個或是更多的類型上。
第二點,映射條件必須對表中的每一行負責,不能存在某一行不被映射到合適的實體類型上。如果你的系統是一個遺留的數據庫,且表中的行由別的系統來創建,你沒有機會條件映射,這種情況會相當的麻煩。 這會發生什么狀況呢?不能映射到基類或派生類的行,將不能被模型訪問到。
第三點,鑒別列不能映射到一個實體屬性上,除非它先被用作一個is not null的映射條件。這一點看上去有點過分嚴格。你可能會問,“如果不能設置鑒別值,那怎么插入一行代表派生類的數據?” ,答案很簡潔,你直接創建一個派生類的實例,然后和添加別的實體類型實例一樣,將其添加到上下文中,對象服務會創建一行擁有合適的鑒別值的插入語句。
此篇到此結束,感謝你的閱讀。本系列由VolcanoCloud翻譯,轉載請注明出處:http://www.cnblogs.com/VolcanoCloud/p/4490841.html
實體框架交流QQ群: 458326058,歡迎有興趣的朋友加入一起交流
謝謝大家的持續關注,我的博客地址:http://www.cnblogs.com/VolcanoCloud/