冠军

导航

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 推荐英文教程,为了帮大家看起来方便一些,简单翻译一下。这是一个系列,共有 8 篇,这是第 7 篇。
 
  1. Entity Framework 4.1 之一 : 基础
  2. Entity Framework 4.1 之二 : 覆盖默认的约定
  3. Entity Framework 4.1 之三 : 贪婪加载和延迟加载
  4. Entity Framework 4.1 之四:复杂类型
  5. Entity Framework 4.1 之五:多对多的关系
  6. Entity Framework 4.1 之六:乐观并发
  7. Entity Framework 4.1 之七:继承
  8. Entity Framework 4.1 之八:绕过 EF 查询映射

在 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 。

posted on 2011-05-08 21:47  冠军  阅读(13264)  评论(8编辑  收藏  举报