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. mount.cifs permission denied

    [root@dev ~]# mount.cifs //192.168.9.155/APP /mnt/APP/ -o user=administrator,pass=dsff#$TTT 在检查帐号密码权 ...

  2. Vue note 2

    1.异步加载组件 一般单页面的缺点是首屏加载比较慢,因为首屏会把所有所需静态资源全部加载,对于中大型项目来说这样可能不是很合理.初步采用异步组件的方式,配合webpack,组件内部可以采用: comp ...

  3. errors collectiions

    c/c++中出现“undefined reference to”的解决 如果提示未定义的函数是某个库的函数.检查库时候已经安装,并在编译命令中采用-l和-L参数导入库. 如果提示未定义的函数是程序中的 ...

  4. SpringCloud-day05-服务调用Ribbon

    6.服务调用Ribbon 6.1Ribbon简介 前面讲了eureka服务注册与发现,但是结合eureka集群的服务调用并没有谈到.这里就要用到Ribbon,结合eureka,来实现服务的调用: Ri ...

  5. Form-encoded method must contain at least one @Field.

    https://blog.csdn.net/liunian823/article/details/80290855 记得之前遇到过这个问题,并且记录笔记了,这次再翻笔记,却没有找到...搜索 了下. ...

  6. 利用gitbush从git上下载代码到本地

    1. 在本地新建一个存放代码的文件夹: 2.进入文件夹,右击Git bush here3 3. 出现以下面板: 4. 输入: git init 5.输入:git clone 文件地址链接 成功,在文件 ...

  7. html初识form表单

    定义和用法 <form> 标签用于为用户输入创建 HTML 表单. 表单能够包含 input 元素,比如文本字段.复选框.单选框.提交按钮等等. 表单用于向服务器传输数据.通过submit ...

  8. Python:a,*args,**kwargs的理解

    1.何时用这些参数? 在任何时候继承类和重写方法时,应当用到’*args’和’**kwargs’将接收到的位置参数和键值参数给父类方法 . 2.一句话清晰说明: a是常规的变量类型,比如int,str ...

  9. Python深拷贝和浅拷贝!

    在python中,对象赋值实际上是对象的引用.当创建一个对象,然后把它赋给另一个变量的时候,python并没有拷贝这个对象,而只是拷贝了这个对象的引用 一般有三种方法, alist=[1,2,3,[& ...

  10. 观察者模式C#实现实例(一)

    1.用例情景 1)定义一个闹钟(目标类),里面我们感兴趣的是时间值times,当times大于9.15时,通知观察者. 2)定义两个观察者,userA,userB,当收到times值时,作出判断,当t ...