Code First的实体继承模式
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的实体继承模式的更多相关文章
- Entity Framework应用:Code First的实体继承模式
Entity Framework的Code First模式有三种实体继承模式 1.Table per Type (TPT)继承 2.Table per Class Hierarchy(TPH)继承 3 ...
- 《ASP.NET MVC4 WEB编程》学习笔记------Entity Framework的Database First、Model First和Code Only三种开发模式
作者:张博出处:http://yilin.cnblogs.com Entity Framework支持Database First.Model First和Code Only三种开发模式,各模式的开发 ...
- js类式继承模式学习心得
最近在学习<JavaScript模式>,感觉里面的5种继承模式写的很好,值得和大家分享. 类式继承模式#1--原型继承 方法 让子函数的原型来继承父函数实例出来的对象 <script ...
- JavaScript 对象的创建和对6种继承模式的理解和遐想
JS中总共有六种继承模式,包括原型链.借用构造函数.组合继承.原型式继承寄生式继承和寄生组合式继承.为了便于理解记忆,我遐想了一个过程,对6中模式进行了简单的阐述. 很长的一个故事,姑且起个名字叫 ...
- JS继承模式粗探
之前提到了JS中比较简单的设计模式,在各种设计模式中被最常使用的工具之一就是原型链的继承.作为OOP的特质之一——继承,今天主要谈谈JS中比较简单的继承方法. 最基础的原型链继承在这里就不复述了,主要 ...
- 【读书笔记】读《JavaScript模式》 - 函数复用模式之现代继承模式
现代继承模式可表述为:其他任何不需要以类的方式考虑得模式. 现代继承方式#1 —— 原型继承之无类继承模式 function object(o) { function F() {}; F.protot ...
- 【读书笔记】读《JavaScript模式》 - 函数复用模式之类式继承模式
实现类式继承的目标是通过构造函数Child()获取来自于另外一个构造函数Parent()的属性,从而创建对象. 1.类式继承模式#1 —— 默认方式(原型指向父函数实例) function Paren ...
- JavaScript中的继承模式总结
一.总结: //js中的几种继承 //原型链的问题,包含引用类型的原型属性会被实例共享,子类型无法给超类型传递参数 function SuperType() { this.colors = [&quo ...
- js继承模式
组合继承是js常用的继承模式,指的是将原型链和借用构造函数的技术结合在一起.其中的思想是使用原型链实现原型属性和方法的继承, 而通过借用构造函数实现对属性的继承. 例子: <script> ...
随机推荐
- Nginx之 try_files 指令
location / { try_files $uri $uri/ /index.php; } 当用户请求 http://localhost/example 时,这里的 $uri 就是 /exampl ...
- 关于Eclipse for Python
学习Python一段时间,一直用Python的IDE进行开发,过程蛮顺利,但是,基于Visual Studio的使用经验,就希望尝试一种更友好的,更方便管理项目的IDE,分别尝试了PyCharm和Ec ...
- 前端笔记-javaScript-3
BOM对象 window对象 所有浏览器都支持 window 对象概念上讲.一个html文档对应一个window对象功能上讲: 控制浏览器窗口的使用上讲: window对象不需要创建对象,直接使用即可 ...
- MySQL一般查询日志或者慢查询日志历史数据的清理
general log&slow query log 对于MySQL的一般查询日志和慢查询日志,开启比较简单,其中公用的一个参数是log_output,log_output控制着慢查询和一般查 ...
- hbase 存储结构和原理
HBase的表结构 建表时要指定的是:表名.列族 建表语句 create 'user_info', 'base_info', 'ext_info' 意思是新建一个表,名称是user_info,包含两个 ...
- jquery遍历----end()方法
定义和用法 end() 方法结束当前链条中的最近的筛选操作,并将匹配元素集还原为之前的状态. 举个栗子: <body> <ul class="first"> ...
- <Dare To Dream> 第四次作业:基于原型的团队项目需求调研与分析
任务1:实施团队项目软件用户调研活动. (1)真实的用户调研对象:生科院大三学生 (2)利用实验七所开发的软件原型:网站原型链接 (3)要有除原型法之外的其他需求获取手段: 访谈法 开会研讨法 (4) ...
- setCapture 使用方法
setCapture 可以捕获到 移动到浏览器外的鼠标事件. 例如拖动过程中,即使鼠标移动到了浏览器外,拖动程序依然可以执行! 作用就是把 把鼠标事件 捕获到 当前文档指定的对象! setCaptur ...
- linux重装rabbitmq的问题
一.卸载 [root@zabbix_server lib]# rpm -qa|grep rabbitmq rabbitmq-server--.noarch [root@zabbix_server li ...
- Java框架spring Boot学习笔记(十):传递数据到html页面的例子
新建一个templates文件夹和index.html <!DOCTYPE html> <html> <head lang="en"> < ...