这是一个系列,共有 8 篇,这是第 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();
}
{
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();
}
{
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 。