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. 百度获取图片 json格式解析

    var h,i: integer; ss, url: string; mem: TMemoryStream; str1: tstringlist; memstr: TStringStream; idd ...

  2. inpuy只能输入数字,并且禁止输入e

    <el-input type="number" onkeyup="this.value=this.value.replace(/[^\d]/g,'')" ...

  3. python基础系列教程,数学基础系列教程,数据分析系列教程,神经网络系列教程,深度学习系列视频教程分享交流

    大家好,我是一个技术爱好者,目前对大数据人工智能很是痴迷,虽然学历只有高中,目前正在大踏步的向着人工智能狂奔,如果你也想学习,那就来吧 我的学习进度python基础(Numpy,pandas,matp ...

  4. HATEOAS

    HATEOAS(Hypermedia as the engine of application state)是 REST 架构风格中最复杂的约束,也是构建成熟 REST 服务的核心.它的重要性在于打破 ...

  5. WCF基础_使用svcutil.exe 工具来生成调用文件

    右键单击一个服务在浏览器中打开时,通常会有这么一段话: ServiceDemo 服务 已创建服务. 若要测试此服务,需要创建一个客户端,并将其用于调用该服务.可以使用下列语法,从命令行中使用 svcu ...

  6. Python学习—数据库篇之练习题

    Mysql测试题 一.表关系 请创建如下表,并创建相关约束 二.操作表 0.在成绩表中同时显示出对应的课程名和学生名 1.自行创建测试数据 2.查询“生物”课程比“物理”课程成绩高的所有学生的学号: ...

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

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

  8. mysql学习笔记--数据库事务

    一.概念 1. 事务是一个不可分割的单元 2. 事务作为一个整体要么一起执行,要么一起回滚 二.事务操作 1. 开启事务 start transaction 或者begin [work] 2. 提交事 ...

  9. Mybatis之批量操作

    首先批量操作的优点是:大大的提高查询的效率. 举个简单的例子:如果在程序中遍历来执行sql的话,这种情况就是有多少行数据就要执行多少条sql,这样导致的效率将是非常低. 如下可能需要40s inser ...

  10. OO_多线程电梯_单元总结

    概述: 面向对象的第二单元是多线程电梯.第一次实现一部傻瓜电梯,每次只送一个人:第二次实现一部可稍带电梯:第三次实现三部可稍带电梯. 一.设计策略 1.第5.6次作业设计思路 第5.6次作业的架构相似 ...