1. 程式人生 > >Entity Framework 4.1 之七:繼承

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; }
publicint Age { get; set; }
}

publicclass Worker : PersonBase
{
publicdecimal AnnualSalary { get; set; }
}

publicclass Retired : PersonBase
{
publicdecimal MonthlyPension { get; set; }
}

你需要告訴模型構建器如何對映到表中。

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");
}

我們使用預設的命名對映約定,模型構建器使用這些資訊用 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 。