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. Leetcode_两数相加_python

    小编从今天起要开始分享一些Leetcode代码,通过好好练习编程能力,争取以后找一份好工作. 题目:两数相加 # Definition for singly-linked list. # class ...

  2. MongoDB 集合(Collection)对应的物理文件

    dbpath下是清一色的collection-n-***与index-n-***开头的物理文件,如何知道某一个集合与其对应与其对应的物理文件? db.collection_name.stats() 返 ...

  3. phxpaxos遇到反复拉取checkpoint但是反复失败的问题,给其它节点造成压力

    原因: 接收checkpoint时与接收普通message共用IOLoop中的队列,当遇到队列满或者超内存时,会造成checkpoint的包随机丢失的问题 解决办法: 遇到checkpoint时不丢弃 ...

  4. ARTS打卡计划第二周-Tips-mysql-binlog-connector-java的使用

    最近发现一个挺不错的框架mysql-binlog-connector-java,可以实时监控binlog的变化. 首先检查mysql的binlog是否开启,在开启的情况下: 引入依赖 <depe ...

  5. tensorflow_目标识别object_detection_api,RuntimeError: main thread is not in main loop,fig = plt.figure(frameon=False)_tkinter.TclError: no display name and no $DISPLAY environment variable

    最近在使用目标识别api,但是报错了: File "/usr/local/lib/python2.7/dist-packages/tensorflow/python/ops/script_o ...

  6. oslo_db使用

    oslo_db是openstak中封装数据库访问sqlachmy的模块,网上搜索的资源并不多,除了openstack官方文档,在实际使用中的例子凤毛麟角. 有感于资源太少,在学习heat源码的过程中, ...

  7. VS2019正式版注册码秘钥

    Visual Studio 2019 EnterpriseBF8Y8-GN2QH-T84XB-QVY3B-RC4DF Visual Studio 2019 ProfessionalNYWVH-HT4X ...

  8. Windows内核开发之串口过滤

    学习了几个月的内核编程,现在对Windows驱动开发又了更加深入的认识,特别是对IRP的分层处理逻辑有了深入认识. 总结起来就几句话: 当irp下来的时候,你要根据实际情况,进行处理 1> 无处 ...

  9. forEach() 和 map() 遍历

    1.forEach()   没有返回值. arr[].forEach(function(value,index,array){ //do something }) 参数:value数组中的当前项, i ...

  10. Android 8.0+ 通知不显示的适配

    最近在 写项目的时候  发现 通知并不会显示的问题,查看资料发现 从Android 8.0开始通知必须加上ChannelId Android O 引入了 通知渠道(Notification Chann ...