Entity Framework 4.1 之七:繼承
原文名稱:Entity Framework 4.1: Inheritance (7)
看到 Entity Framework 4.1 推薦英文教程,為了幫大家看起來方便一些,簡單翻譯一下。這是一個系列,共有 8 篇,這是第 7 篇。在 ORM 文獻中,有三種方式將物件的繼承關係對映到表中。
- 每個型別一張表 TPT: 在繼承層次中的每個類都分別對映到資料庫中的一張表,彼此之間通過外來鍵關聯。
- 繼承層次中所有的型別一張表 TPH:對於繼承層次中的所有型別都對映到一張表中,所有的資料都在這張表中。
- 每種實現型別一張表 TPC: 有點像其他兩個的混合,對於每種實現型別對映到一張表,抽象型別像 TPH 一樣展開到表中。
這裡我將討論 TPT 和 TPH,EF 的好處是可以混合使用這些方式。
TPT 方式
讓我們從每種型別一張表開始,我定義了一個簡單的繼承層次,一個抽象基類和兩個派生類。
publicabstractclass PersonBase{
publicint PersonID { get; set; }
[Required]
publicstring FirstName { get; set; }
[Required]
publicstring LastName { get; set; }
}
publicclass Worker : PersonBase
{
publicdecimal AnnualSalary { get; set; }
}
publicclass Retired : PersonBase
{
publicdecimal MonthlyPension { get; set; }
}
你需要告訴模型構建器如何對映到表中。
protectedoverridevoid OnModelCreating(DbModelBuilder modelBuilder){
modelBuilder.Entity<PersonBase>().HasKey(x => x.PersonID);
modelBuilder.Entity<PersonBase>().Property(x => x.PersonID)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
// TPT mapping modelBuilder.Entity<PersonBase>().ToTable("tpt.Person");
modelBuilder.Entity<Worker>().ToTable("tpt.Worker");
modelBuilder.Entity<Retired>().ToTable("tpt.Retired");
}
我們使用預設的命名對映約定,模型構建器使用這些資訊用 TPT 來建立資料庫。
我們使用模型來跑一些程式碼,讓我們理解如何使用上面的對映,基本上,我們僅僅使用一個 DbSet,一個 PersonBase 的集合,EF 會管理每一個成員的實際型別。
publicstaticvoid ManageTPT(){
using (var context1 =new TptContext())
{
var worker =new Worker
{
AnnualSalary =20000,
Age =25,
FirstName ="Joe",
LastName ="Plumber"
};
var retired =new Retired
{
MonthlyPension =1500,
Age =22,
FirstName ="Mike",
LastName ="Smith"
};
// Make sure the tables are empty… foreach (var entity in context1.Persons)
{
context1.Persons.Remove(entity);
}
context1.Persons.Add(worker);
context1.Persons.Add(retired);
context1.SaveChanges();
}
using (var context2 =new TptContext())
{
Console.WriteLine("Persons count: "+ context2.Persons.OfType<PersonBase>().Count());
Console.WriteLine("Worker: "+ context2.Persons.OfType<Worker>().Count());
Console.WriteLine("Retired: "+ context2.Persons.OfType<Retired>().Count());
}
}
這真的很強大,我們可以通過訪問 Workers 來僅僅訪問 Workers 表。
TPH 方式
TPH 是 EF 實際上預設支援的。我們可以簡單地註釋到前面例子中的對錶的對映來使用預設的機制。
protectedoverridevoid OnModelCreating(DbModelBuilder modelBuilder){
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<PersonBase>().HasKey(x => x.PersonID);
modelBuilder.Entity<PersonBase>().Property(x => x.PersonID)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
// TPT mapping
//modelBuilder.Entity<PersonBase>().ToTable("tpt.Person");
//modelBuilder.Entity<Worker>().ToTable("tpt.Worker");
//modelBuilder.Entity<Retired>().ToTable("tpt.Retired"); }
結果是現在使用一張表來影射整個的繼承層次。
注意到整個的層次被展開到一張表中。基類中沒有的屬性被自動標記為可空。還有一個額外的區分列,如果執行前面的例子,我們將會看到這個區分列的內容。
當 EF 讀取一行的時候,區分列被 EF 用來知道應該建立例項的型別,因為現在所有的類都被對映到了一張表中。
也可以覆蓋這一點,下面我們看一下同時混合使用 TPH 和 TPT。我定義了 Worker 的兩個子類,我希望將這兩個類和 Worker 基類對映到一張表。
publicclass Manager : Worker{
publicint? ManagedEmployeesCount { get; set; }
}
publicclass FreeLancer : Worker
{
[Required]
publicstring IncCompanyName { get; set; }
}
注意到每一個屬性都必須是可空的。這在 TPH 中非常不方便:每一個屬性都必須是可空的。現在我們使用模型構建器來完成。
protectedoverridevoid OnModelCreating(DbModelBuilder modelBuilder){
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<PersonBase>().HasKey(x => x.PersonID);
modelBuilder.Entity<PersonBase>().Property(x => x.PersonID)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
// TPT mapping modelBuilder.Entity<PersonBase>().ToTable("tpt.Person");
modelBuilder.Entity<Retired>().ToTable("tpt.Retired");
// TPH mapping modelBuilder.Entity<Worker>()
.Map<FreeLancer>(m => m.Requires(f => f.IncCompanyName).HasValue())
.Map<Manager>(m => m.Requires(ma => ma.ManagedEmployeesCount).HasValue())
.ToTable("tph.Worker");
}
這裡我使用了一種區分的方法:與預設不同,我要求屬於類的列是非空的列。
使用者不需要與 TPT 區分,甚至在修改了對映之後不會影響使用的程式碼。
publicstaticvoid ManageTPH(){
using (var context1 =new HierarchyContext())
{
var worker =new Worker
{
AnnualSalary =20000,
Age =25,
FirstName ="Joe",
LastName ="Plumber"
};
var freeLancer =new FreeLancer
{
Age =22,
FirstName ="Mike",
LastName ="Smith",
IncCompanyName ="Mike & Mike Inc"
};
var manager =new Manager
{
Age =43,
FirstName ="George",
LastName ="Costanza",
ManagedEmployeesCount =12
};
// Make sure the tables are empty… foreach (var entity in context1.Persons)
{
context1.Persons.Remove(entity);
}
context1.Persons.Add(worker);
context1.Persons.Add(freeLancer);
context1.Persons.Add(manager);
context1.SaveChanges();
}
using (var context2 =new HierarchyContext())
{
Console.WriteLine("Persons count: "+ context2.Persons.OfType<PersonBase>().Count());
Console.WriteLine("Worker: "+ context2.Persons.OfType<Worker>().Count());
Console.WriteLine("Retired: "+ context2.Persons.OfType<Retired>().Count());
Console.WriteLine("FreeLancer: "+ context2.Persons.OfType<FreeLancer>().Count());
Console.WriteLine("Manager: "+ context2.Persons.OfType<Manager>().Count());
}
}
SQL 中的架構如下,這裡混合使用了 TPT 和 TPH 。