Entity Framework的Code First模式有三种实体继承模式

1、Table per Type (TPT)继承

2、Table per Class Hierarchy(TPH)继承

3、Table per Concrete Class (TPC)继承

一、TPT继承模式

当领域实体类有继承关系时,TPT继承很有用,我们想把这些实体类模型持久化到数据库中,这样,每个领域实体都会映射到单独的一张表中。这些表会使用一对一关系相互关联,数据库会通过一个共享的主键维护这个关系。

假设有这么一个场景:一个组织维护了一个部门工作的所有人的数据库,这些人有些是拿着固定工资的员工,有些是按小时付费的临时工,要持久化这个场景,我们要创建三个领域实体:Person、Employee和Vendor类。Person类是基类,另外两个类会继承自Person类。实体类结构如下:

1、Person类

 1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading.Tasks;
6
7 namespace TPTPattern.Model
8 {
9 public class Person
10 {
11 public int Id { get; set; }
12
13 public string Name { get; set; }
14
15 public string Email { get; set; }
16
17 public string PhoneNumber { get; set; }
18 }
19 }

Employee类结构

 1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel.DataAnnotations.Schema;
4 using System.Linq;
5 using System.Text;
6 using System.Threading.Tasks;
7
8 namespace TPTPattern.Model
9 {
10 [Table("Employee")]
11 public class Employee :Person
12 {
13 /// <summary>
14 /// 薪水
15 /// </summary>
16 public decimal Salary { get; set; }
17 }
18 }

Vendor类结构

 1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel.DataAnnotations.Schema;
4 using System.Linq;
5 using System.Text;
6 using System.Threading.Tasks;
7
8 namespace TPTPattern.Model
9 {
10 [Table("Vendor")]
11 public class Vendor :Person
12 {
13 /// <summary>
14 /// 每小时的薪水
15 /// </summary>
16 public decimal HourlyRate { get; set; }
17 }
18 }

在VS中的类图如下:

对于Person类,我们使用EF的默认约定来映射到数据库,而对Employee和Vendor类,我们使用了数据注解,将它们映射为我们想要的表名。

然后我们需要创建自己的数据库上下文类,数据库上下文类定义如下:

 1 using System;
2 using System.Collections.Generic;
3 using System.Data.Entity;
4 using System.Linq;
5 using System.Text;
6 using System.Threading.Tasks;
7 using TPTPattern.Model;
8
9 namespace TPTPattern.EFDatabaseContext
10 {
11 public class EFDbContext :DbContext
12 {
13 public EFDbContext()
14 : base("name=Default")
15 { }
16
17 public DbSet<Person> Persons { get; set; }
18 }
19 }

在上面的上下文中,我们只添加了实体类Person的DbSet,没有添加另外两个实体类的DbSet。因为其它的两个领域模型都是从这个模型派生的,所以我们也就相当于将其它两个类添加到了DbSet集合中了,这样EF会使用多态性来使用实际的领域模型。当然,也可以使用Fluent API和实体伙伴类来配置映射细节信息。

2、使用数据迁移创建数据库

使用数据迁移创建数据库后查看数据库表结构:

在TPT继承中,我们想为每个领域实体类创建单独的一张表,这些表共享一个主键。因此生成的数据库关系图表如下:

3、填充数据

现在我们使用这些领域实体来创建一个Employee和Vendor类来填充数据,Program类定义如下:

 1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading.Tasks;
6 using TPTPattern.EFDatabaseContext;
7 using TPTPattern.Model;
8
9 namespace TPTPattern
10 {
11 class Program
12 {
13 static void Main(string[] args)
14 {
15 using (var context = new EFDbContext())
16 {
17 Employee emp = new Employee()
18 {
19 Name="李白",
20 Email="LiBai@163.com",
21 PhoneNumber="18754145782",
22 Salary=2345m
23 };
24
25 Vendor vendor = new Vendor()
26 {
27 Name="杜甫",
28 Email="DuFu@qq.com",
29 PhoneNumber="18234568123",
30 HourlyRate=456m
31 };
32
33 context.Persons.Add(emp);
34 context.Persons.Add(vendor);
35 context.SaveChanges();
36 }
37
38 Console.WriteLine("信息录入成功");
39 }
40 }
41 }

查询数据库填充后的数据:

我们可以看到每个表都包含单独的数据,这些表之间都有一个共享的主键。因而这些表之间都是一对一的关系。

注:TPT模式主要应用在一对一模式下。

二、TPH模式

当领域实体有继承关系时,但是我们想将来自所有的实体类的数据保存到单独的一张表中时,TPH继承很有用。从领域实体的角度,我们的模型类的继承关系仍然像上面的截图一样:

但是从数据库的角度讲,应该只有一张表保存数据。因此,最终生成的数据库的样子应该是下面这样的:

注意:从数据库的角度看,这种模式很不优雅,因为我们将无关的数据保存到了单张表中,我们的表是不标准的。如果我们使用这种方法,那么总会存在null值的冗余列。

1、创建有继承关系的实体类

现在我们创建实体类来实现该继承,注意:这次创建的三个实体类和之前创建的只是没有了类上面的数据注解,这样它们就会映射到数据库的单张表中(EF会默认使用父类的DbSet属性名或复数形式作为表名,并且将派生类的属性映射到那张表中),类结构如下:

2、创建数据上下文

 1 using System;
2 using System.Collections.Generic;
3 using System.Data.Entity;
4 using System.Linq;
5 using System.Text;
6 using System.Threading.Tasks;
7 using TPHPattern.Model;
8
9 namespace TPHPattern.EFDatabaseContext
10 {
11 public class EFDbContext :DbContext
12 {
13 public EFDbContext()
14 : base("name=Default")
15 { }
16
17 public DbSet<Person> Persons { get; set; }
18
19 public DbSet<Employee> Employees { get; set; }
20
21 public DbSet<Vendor> Vendors { get; set; }
22 }
23 }

3、使用数据迁移创建数据库

使用数据迁移生成数据库以后,会发现数据库中只有一张表,而且三个实体类中的字段都在这张表中了, 创建后的数据库表结构如下:

注意:查看生成的表结构,会发现生成的表中多了一个Discriminator字段,它是用来找到记录的实际类型,即从Person表中找到Employee或者Vendor。

4、不使用默认生成的区别多张表的类型

使用Fluent API,修改数据上下文类,修改后的类定义如下:

 1 using System;
2 using System.Collections.Generic;
3 using System.Data.Entity;
4 using System.Linq;
5 using System.Text;
6 using System.Threading.Tasks;
7 using TPHPattern.Model;
8
9 namespace TPHPattern.EFDatabaseContext
10 {
11 public class EFDbContext :DbContext
12 {
13 public EFDbContext()
14 : base("name=Default")
15 { }
16
17 public DbSet<Person> Persons { get; set; }
18
19 public DbSet<Employee> Employees { get; set; }
20
21 public DbSet<Vendor> Vendors { get; set; }
22
23 protected override void OnModelCreating(DbModelBuilder modelBuilder)
24 {
25 // 强制指定PersonType是鉴别器 1代表全职职员 2代表临时工
26 modelBuilder.Entity<Person>()
27 .Map<Employee>(m => m.Requires("PersonType").HasValue(1))
28 .Map<Vendor>(m => m.Requires("PersonType").HasValue(2));
29 base.OnModelCreating(modelBuilder);
30 }
31 }
32 }

重新使用数据迁移把实体持久化到数据库,持久化以后的数据库表结构:

生成的PersonType列的类型是int。

5、填充数据

 1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading.Tasks;
6 using TPHPattern.EFDatabaseContext;
7 using TPHPattern.Model;
8
9 namespace TPHPattern
10 {
11 class Program
12 {
13 static void Main(string[] args)
14 {
15 using (var context = new EFDbContext())
16 {
17 Employee emp = new Employee()
18 {
19 Name = "李白",
20 Email = "LiBai@163.com",
21 PhoneNumber = "18754145782",
22 Salary = 2345m
23 };
24
25 Vendor vendor = new Vendor()
26 {
27 Name = "杜甫",
28 Email = "DuFu@qq.com",
29 PhoneNumber = "18234568123",
30 HourlyRate = 456m
31 };
32
33 context.Persons.Add(emp);
34 context.Persons.Add(vendor);
35 context.SaveChanges();
36 }
37
38 Console.WriteLine("信息录入成功");
39 }
40 }
41 }

6、查询数据

注意:TPH模式和TPT模式相比,TPH模式只是少了使用数据注解或者Fluent API配置子类的表名。因此,如果我们没有在具有继承关系的实体之间提供确切的配置,那么EF会默认将其对待成TPH模式,并把数据放到单张表中。

三、TPC模式

当多个领域实体类派生自一个基类实体,并且我们想将所有具体类的数据分别保存在各自的表中,以及抽象基类实体在数据库中没有对应的表时,使用TPC继承模式。实体模型还是和之前的一样。

然而,从数据库的角度看,只有所有具体类所对应的表,而没有抽象类对应的表。生成的数据库如下图:

1、创建实体类

创建领域实体类,这里Person基类应该是抽象的,其他的地方都和上面一样:

2、配置数据上下文

接下来就是应该配置数据库上下文了,如果我们只在数据库上下文中添加了Person的DbSet泛型属性集合,那么EF会当作TPH继承处理,如果我们需要实现TPC继承,那么还需要使用Fluent API来配置映射(当然也可以使用配置伙伴类),数据库上下文类定义如下:

 1 using System;
2 using System.Collections.Generic;
3 using System.Data.Entity;
4 using System.Linq;
5 using System.Text;
6 using System.Threading.Tasks;
7 using TPCPattern.Model;
8
9 namespace TPCPattern.EFDatabaseContext
10 {
11 public class EFDbContext :DbContext
12 {
13 public EFDbContext()
14 : base("name=Default")
15 { }
16
17 public virtual DbSet<Person> Persons { get; set; }
18
19 protected override void OnModelCreating(DbModelBuilder modelBuilder)
20 {
21 //MapInheritedProperties表示继承以上所有的属性
22 modelBuilder.Entity<Employee>().Map(m =>
23 {
24 m.MapInheritedProperties();
25 m.ToTable("Employees");
26 });
27 modelBuilder.Entity<Vendor>().Map(m =>
28 {
29 m.MapInheritedProperties();
30 m.ToTable("Vendors");
31 });
32 base.OnModelCreating(modelBuilder);
33 }
34 }
35 }

上面的代码中,MapInheritedProperties方法将继承的属性映射到表中,然后我们根据不同的对象类型映射到不同的表中。

3、使用数据迁移生成数据库

生成的数据库表结构如下:

查看生成的表结构会发现,生成的数据库中只有具体类对应的表,而没有抽象基类对应的表。具体实体类对应的表中有所有抽象基类里面的字段。

4、填充数据

 1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading.Tasks;
6 using TPCPattern.EFDatabaseContext;
7 using TPCPattern.Model;
8
9 namespace TPCPattern
10 {
11 class Program
12 {
13 static void Main(string[] args)
14 {
15 using (var context = new EFDbContext())
16 {
17 Employee emp = new Employee()
18 {
19 Name = "李白",
20 Email = "LiBai@163.com",
21 PhoneNumber = "18754145782",
22 Salary = 2345m
23 };
24
25 Vendor vendor = new Vendor()
26 {
27 Name = "杜甫",
28 Email = "DuFu@qq.com",
29 PhoneNumber = "18234568123",
30 HourlyRate = 456m
31 };
32
33 context.Persons.Add(emp);
34 context.Persons.Add(vendor);
35 context.SaveChanges();
36 }
37
38 Console.WriteLine("信息录入成功");
39 }
40 }
41 }

查询数据库:

注意:虽然数据是插入到数据库了,但是运行程序时也出现了异常,异常信息见下图。出现该异常的原因是EF尝试去访问抽象类中的值,它会找到两个具有相同Id的记录,然而Id列被识别为主键,因而具有相同主键的两条记录就会产生问题。这个异常清楚地表明了存储或者数据库生成的Id列对TPC继承无效。

如果我们想使用TPC继承,那么要么使用基于GUID的Id,要么从应用程序中传入Id,或者使用能够维护对多张表自动生成的列的唯一性的某些数据库机制。

Code First的实体继承模式的更多相关文章

  1. Entity Framework应用:Code First的实体继承模式

    Entity Framework的Code First模式有三种实体继承模式 1.Table per Type (TPT)继承 2.Table per Class Hierarchy(TPH)继承 3 ...

  2. 《ASP.NET MVC4 WEB编程》学习笔记------Entity Framework的Database First、Model First和Code Only三种开发模式

    作者:张博出处:http://yilin.cnblogs.com Entity Framework支持Database First.Model First和Code Only三种开发模式,各模式的开发 ...

  3. js类式继承模式学习心得

    最近在学习<JavaScript模式>,感觉里面的5种继承模式写的很好,值得和大家分享. 类式继承模式#1--原型继承 方法 让子函数的原型来继承父函数实例出来的对象 <script ...

  4. JavaScript 对象的创建和对6种继承模式的理解和遐想

      JS中总共有六种继承模式,包括原型链.借用构造函数.组合继承.原型式继承寄生式继承和寄生组合式继承.为了便于理解记忆,我遐想了一个过程,对6中模式进行了简单的阐述. 很长的一个故事,姑且起个名字叫 ...

  5. JS继承模式粗探

    之前提到了JS中比较简单的设计模式,在各种设计模式中被最常使用的工具之一就是原型链的继承.作为OOP的特质之一——继承,今天主要谈谈JS中比较简单的继承方法. 最基础的原型链继承在这里就不复述了,主要 ...

  6. 【读书笔记】读《JavaScript模式》 - 函数复用模式之现代继承模式

    现代继承模式可表述为:其他任何不需要以类的方式考虑得模式. 现代继承方式#1 —— 原型继承之无类继承模式 function object(o) { function F() {}; F.protot ...

  7. 【读书笔记】读《JavaScript模式》 - 函数复用模式之类式继承模式

    实现类式继承的目标是通过构造函数Child()获取来自于另外一个构造函数Parent()的属性,从而创建对象. 1.类式继承模式#1 —— 默认方式(原型指向父函数实例) function Paren ...

  8. JavaScript中的继承模式总结

    一.总结: //js中的几种继承 //原型链的问题,包含引用类型的原型属性会被实例共享,子类型无法给超类型传递参数 function SuperType() { this.colors = [&quo ...

  9. js继承模式

    组合继承是js常用的继承模式,指的是将原型链和借用构造函数的技术结合在一起.其中的思想是使用原型链实现原型属性和方法的继承, 而通过借用构造函数实现对属性的继承. 例子: <script> ...

随机推荐

  1. python中线程的知识点

    什么是线程? 程序的执行线路.每个进程默认有一条线程.线程包含了程序的具体步骤. 多线程就是一个进程中有除主线程(默认线程)外还有多个线程. 线程与进程的关系(进程包含线程,而线程依赖进程存在) 1. ...

  2. 【译】为什么BERT有3个嵌入层,它们都是如何实现的

    目录 引言 概览 Token Embeddings 作用 实现 Segment Embeddings 作用 实现 Position Embeddings 作用 实现 合成表示 结论 参考文献 本文翻译 ...

  3. 将ipad作为电脑拓展屏或分屏的简单方法

    用Ipad实现电脑分屏的方法是挺简单的,但鉴于部分小白找不到合适的门路,在此重新分享一下. 需要的装备:  ipad   电脑   数据连接线 方法:某宝上搜索 duet display ,只需1元左 ...

  4. 解决IIS配置问题

    解决网站运行一段时间会变慢的问题 http://blog.csdn.net/rryqsh/article/details/8156558 1. IIS 7 应用程序池自动回收关闭的解决方案 如果你正在 ...

  5. Python中的进程

    进程 from multiprocessing import Process import time def start(name): time.sleep(1) print('hello', nam ...

  6. FormsAuthentication 票据前后台登录导致掉线

    一.前后台的用户信息都是采用.NET自带的FormsAuthentication 的ticket存取用户信息, 但是如果前后台用相同的用户使用票据这个会导致一方登陆后另一方会掉线,需要重新登陆. 二. ...

  7. Polar Code(1)极化码SC译码迭代公式的理解

    采用对数似然比求解的迭代公式推导: 考虑 如上图,将L的部分看为一个整体,用 exp(a)和exp(b)代替,并对式子左右都取对数,则公式变为如下所示: 对数似然比 上述公式等效一下公式: 进一步可等 ...

  8. 用BlockingQueue实现的简单发布订阅模式

  9. SpringMVC避免IE执行AJAX,返回JSON出现下载文件

  10. jq 字符串去除空格

    1.去除首尾空格: var txt = $('#Txt').val().trim(); txt = txt.replace(/(^\s*)|(\s*$)/g, ""); 2.去除所 ...