Entity Framework 4.1 之七:继承

在 ORM 文献中,有三种方式将对象的继承关系映射到表中。

  • 每个类型一张表 TPT: 在继承层次中的每个类都分别映射到数据库中的一张表,彼此之间通过外键关联。
  • 继承层次中所有的类型一张表 TPH:对于继承层次中的所有类型都映射到一张表中,所有的数据都在这张表中。
  • 每种实现类型一张表 TPC: 有点像其他两个的混合,对于每种实现类型映射到一张表,抽象类型像 TPH 一样展开到表中。

这里我将讨论 TPT 和 TPH,EF 的好处是可以混合使用这些方式。

TPT 方式

让我们从每种类型一张表开始,我定义了一个简单的继承层次,一个抽象基类和两个派生类。

public abstract class PersonBase
{
public int PersonID { get ; set ; }
[Required]
public string FirstName { get ; set ; }
[Required]
public string LastName { get ; set ; }
public int Age { get ; set ; }
}

public class Worker : PersonBase
{
public decimal AnnualSalary { get ; set ; }
}

public class Retired : PersonBase
{
public decimal MonthlyPension { get ; set ; }
}

你需要告诉模型构建器如何映射到表中。

protected override void 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 会管理每一个成员的实际类型。

public static void 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 实际上默认支持的。我们可以简单地注释到前面例子中的对表的映射来使用默认的机制。

protected override void 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 基类映射到一张表。

public class Manager : Worker
{
public int ? ManagedEmployeesCount { get ; set ; }
}

public class FreeLancer : Worker
{
[Required]
public string IncCompanyName { get ; set ; }
}

注意到每一个属性都必须是可空的。这在 TPH 中非常不方便:每一个属性都必须是可空的。现在我们使用模型构建器来完成。

protected override void 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 区分,甚至在修改了映射之后不会影响使用的代码。

public static void 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 。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值