Entity Framework 4.1 之七:繼承
原文名稱:Entity Framework 4.1: Inheritance (7)
原文地址:http://vincentlauzon.wordpress.com/2011/04/19/entity-framework-4-1-inheritance-7/
- Entity Framework 4.1 之一 : 基礎
- Entity Framework 4.1 之二 : 覆蓋默認的約定
- Entity Framework 4.1 之三 : 貪婪加載和延遲加載
- Entity Framework 4.1 之四:復雜類型
- Entity Framework 4.1 之五:多對多的關系
- Entity Framework 4.1 之六:樂觀并發
- Entity Framework 4.1 之七:繼承
- Entity Framework 4.1 之八:繞過 EF 查詢映射
在 ORM 文獻中,有三種方式將對象的繼承關系映射到表中。
- 每個類型一張表 TPT: 在繼承層次中的每個類都分別映射到數據庫中的一張表,彼此之間通過外鍵關聯。
- 繼承層次中所有的類型一張表 TPH:對于繼承層次中的所有類型都映射到一張表中,所有的數據都在這張表中。
- 每種實現類型一張表 TPC: 有點像其他兩個的混合,對于每種實現類型映射到一張表,抽象類型像 TPH 一樣展開到表中。
這里我將討論 TPT 和 TPH,EF 的好處是可以混合使用這些方式。
TPT 方式
讓我們從每種類型一張表開始,我定義了一個簡單的繼承層次,一個抽象基類和兩個派生類。
{
publicint PersonID { get; set; }
[Required]
publicstring FirstName { get; set; }
[Required]
publicstring LastName { get; set; }
publicint Age { get; set; }
}
publicclass Worker : PersonBase
{
publicdecimal AnnualSalary { get; set; }
}
publicclass Retired : PersonBase
{
publicdecimal MonthlyPension { get; set; }
}
你需要告訴模型構建器如何映射到表中。
{
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");
}
我們使用默認的命名映射約定,模型構建器使用這些信息用 TPT 來創建數據庫。

我們使用模型來跑一些代碼,讓我們理解如何使用上面的映射,基本上,我們僅僅使用一個 DbSet,一個 PersonBase 的集合,EF 會管理每一個成員的實際類型。
{
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 實際上默認支持的。我們可以簡單地注釋到前面例子中的對表的映射來使用默認的機制。
{
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 基類映射到一張表。
{
publicint? ManagedEmployeesCount { get; set; }
}
publicclass FreeLancer : Worker
{
[Required]
publicstring IncCompanyName { get; set; }
}
注意到每一個屬性都必須是可空的。這在 TPH 中非常不方便:每一個屬性都必須是可空的。現在我們使用模型構建器來完成。
{
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 區分,甚至在修改了映射之后不會影響使用的代碼。
{
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 。

浙公網安備 33010602011771號