Entity Framework应用: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类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace TPTPattern.Model
{
public class Person
{
public int Id { get; set; } public string Name { get; set; } public string Email { get; set; } public string PhoneNumber { get; set; }
}
}
Employee类结构
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace TPTPattern.Model
{
[Table("Employee")]
public class Employee :Person
{
/// <summary>
/// 薪水
/// </summary>
public decimal Salary { get; set; }
}
}
Vendor类结构
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace TPTPattern.Model
{
[Table("Vendor")]
public class Vendor :Person
{
/// <summary>
/// 每小时的薪水
/// </summary>
public decimal HourlyRate { get; set; }
}
}
在VS中的类图如下:

对于Person类,我们使用EF的默认约定来映射到数据库,而对Employee和Vendor类,我们使用了数据注解,将它们映射为我们想要的表名。
然后我们需要创建自己的数据库上下文类,数据库上下文类定义如下:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TPTPattern.Model; namespace TPTPattern.EFDatabaseContext
{
public class EFDbContext :DbContext
{
public EFDbContext()
: base("name=Default")
{ } public DbSet<Person> Persons { get; set; }
}
}
在上面的上下文中,我们只添加了实体类Person的DbSet,没有添加另外两个实体类的DbSet。因为其它的两个领域模型都是从这个模型派生的,所以我们也就相当于将其它两个类添加到了DbSet集合中了,这样EF会使用多态性来使用实际的领域模型。当然,也可以使用Fluent API和实体伙伴类来配置映射细节信息。
2、使用数据迁移创建数据库
使用数据迁移创建数据库后查看数据库表结构:

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

3、填充数据
现在我们使用这些领域实体来创建一个Employee和Vendor类来填充数据,Program类定义如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TPTPattern.EFDatabaseContext;
using TPTPattern.Model; namespace TPTPattern
{
class Program
{
static void Main(string[] args)
{
using (var context = new EFDbContext())
{
Employee emp = new Employee()
{
Name="李白",
Email="LiBai@163.com",
PhoneNumber="",
Salary=2345m
}; Vendor vendor = new Vendor()
{
Name="杜甫",
Email="DuFu@qq.com",
PhoneNumber="",
HourlyRate=456m
}; context.Persons.Add(emp);
context.Persons.Add(vendor);
context.SaveChanges();
} Console.WriteLine("信息录入成功");
}
}
}
查询数据库填充后的数据:

我们可以看到每个表都包含单独的数据,这些表之间都有一个共享的主键。因而这些表之间都是一对一的关系。
注:TPT模式主要应用在一对一模式下。
二、TPH模式
当领域实体有继承关系时,但是我们想将来自所有的实体类的数据保存到单独的一张表中时,TPH继承很有用。从领域实体的角度,我们的模型类的继承关系仍然像上面的截图一样:

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

注意:从数据库的角度看,这种模式很不优雅,因为我们将无关的数据保存到了单张表中,我们的表是不标准的。如果我们使用这种方法,那么总会存在null值的冗余列。
1、创建有继承关系的实体类
现在我们创建实体类来实现该继承,注意:这次创建的三个实体类和之前创建的只是没有了类上面的数据注解,这样它们就会映射到数据库的单张表中(EF会默认使用父类的DbSet属性名或复数形式作为表名,并且将派生类的属性映射到那张表中),类结构如下:

2、创建数据上下文
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TPHPattern.Model; namespace TPHPattern.EFDatabaseContext
{
public class EFDbContext :DbContext
{
public EFDbContext()
: base("name=Default")
{ } public DbSet<Person> Persons { get; set; } public DbSet<Employee> Employees { get; set; } public DbSet<Vendor> Vendors { get; set; }
}
}
3、使用数据迁移创建数据库
使用数据迁移生成数据库以后,会发现数据库中只有一张表,而且三个实体类中的字段都在这张表中了, 创建后的数据库表结构如下:

注意:查看生成的表结构,会发现生成的表中多了一个Discriminator字段,它是用来找到记录的实际类型,即从Person表中找到Employee或者Vendor。
4、不使用默认生成的区别多张表的类型
使用Fluent API,修改数据上下文类,修改后的类定义如下:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TPHPattern.Model; namespace TPHPattern.EFDatabaseContext
{
public class EFDbContext :DbContext
{
public EFDbContext()
: base("name=Default")
{ } public DbSet<Person> Persons { get; set; } public DbSet<Employee> Employees { get; set; } public DbSet<Vendor> Vendors { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// 强制指定PersonType是鉴别器 1代表全职职员 2代表临时工
modelBuilder.Entity<Person>()
.Map<Employee>(m => m.Requires("PersonType").HasValue())
.Map<Vendor>(m => m.Requires("PersonType").HasValue());
base.OnModelCreating(modelBuilder);
}
}
}
重新使用数据迁移把实体持久化到数据库,持久化以后的数据库表结构:

生成的PersonType列的类型是int。
5、填充数据
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TPHPattern.EFDatabaseContext;
using TPHPattern.Model; namespace TPHPattern
{
class Program
{
static void Main(string[] args)
{
using (var context = new EFDbContext())
{
Employee emp = new Employee()
{
Name = "李白",
Email = "LiBai@163.com",
PhoneNumber = "",
Salary = 2345m
}; Vendor vendor = new Vendor()
{
Name = "杜甫",
Email = "DuFu@qq.com",
PhoneNumber = "",
HourlyRate = 456m
}; context.Persons.Add(emp);
context.Persons.Add(vendor);
context.SaveChanges();
} Console.WriteLine("信息录入成功");
}
}
}
6、查询数据

注意:TPH模式和TPT模式相比,TPH模式只是少了使用数据注解或者Fluent API配置子类的表名。因此,如果我们没有在具有继承关系的实体之间提供确切的配置,那么EF会默认将其对待成TPH模式,并把数据放到单张表中。
三、TPC模式
当多个领域实体类派生自一个基类实体,并且我们想将所有具体类的数据分别保存在各自的表中,以及抽象基类实体在数据库中没有对应的表时,使用TPC继承模式。实体模型还是和之前的一样。
然而,从数据库的角度看,只有所有具体类所对应的表,而没有抽象类对应的表。生成的数据库如下图:

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

2、配置数据上下文
接下来就是应该配置数据库上下文了,如果我们只在数据库上下文中添加了Person的DbSet泛型属性集合,那么EF会当作TPH继承处理,如果我们需要实现TPC继承,那么还需要使用Fluent API来配置映射(当然也可以使用配置伙伴类),数据库上下文类定义如下:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TPCPattern.Model; namespace TPCPattern.EFDatabaseContext
{
public class EFDbContext :DbContext
{
public EFDbContext()
: base("name=Default")
{ } public virtual DbSet<Person> Persons { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//MapInheritedProperties表示继承以上所有的属性
modelBuilder.Entity<Employee>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Employees");
});
modelBuilder.Entity<Vendor>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Vendors");
});
base.OnModelCreating(modelBuilder);
}
}
}
上面的代码中,MapInheritedProperties方法将继承的属性映射到表中,然后我们根据不同的对象类型映射到不同的表中。
3、使用数据迁移生成数据库
生成的数据库表结构如下:

查看生成的表结构会发现,生成的数据库中只有具体类对应的表,而没有抽象基类对应的表。具体实体类对应的表中有所有抽象基类里面的字段。
4、填充数据
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TPCPattern.EFDatabaseContext;
using TPCPattern.Model; namespace TPCPattern
{
class Program
{
static void Main(string[] args)
{
using (var context = new EFDbContext())
{
Employee emp = new Employee()
{
Name = "李白",
Email = "LiBai@163.com",
PhoneNumber = "",
Salary = 2345m
}; Vendor vendor = new Vendor()
{
Name = "杜甫",
Email = "DuFu@qq.com",
PhoneNumber = "",
HourlyRate = 456m
}; context.Persons.Add(emp);
context.Persons.Add(vendor);
context.SaveChanges();
} Console.WriteLine("信息录入成功");
}
}
}
查询数据库:

注意:虽然数据是插入到数据库了,但是运行程序时也出现了异常,异常信息见下图。出现该异常的原因是EF尝试去访问抽象类中的值,它会找到两个具有相同Id的记录,然而Id列被识别为主键,因而具有相同主键的两条记录就会产生问题。这个异常清楚地表明了存储或者数据库生成的Id列对TPC继承无效。
如果我们想使用TPC继承,那么要么使用基于GUID的Id,要么从应用程序中传入Id,或者使用能够维护对多张表自动生成的列的唯一性的某些数据库机制。

Entity Framework应用:Code First的实体继承模式的更多相关文章
- Code First的实体继承模式
Entity Framework的Code First模式有三种实体继承模式 1.Table per Type (TPT)继承 2.Table per Class Hierarchy(TPH)继承 3 ...
- Entity Framework(EF) Code First将实体中的string属性映射成text类型的几种方式
1.通过ColumnType属性设置 [Column(TypeName="text")] public string Text { get; set; } 在进行以上属性设置时,请 ...
- 【极力分享】[C#/.NET]Entity Framework(EF) Code First 多对多关系的实体增,删,改,查操作全程详细示例【转载自https://segmentfault.com/a/1190000004152660】
[C#/.NET]Entity Framework(EF) Code First 多对多关系的实体增,删,改,查操作全程详细示例 本文我们来学习一下在Entity Framework中使用Cont ...
- [C#/.NET]Entity Framework(EF) Code First 多对多关系的实体增,删,改,查操作全程详细示例
本文我们来学习一下在Entity Framework中使用Context删除多对多关系的实体是如何来实现的.我们将以一个具体的控制台小实例来了解和学习整个实现Entity Framework 多对多关 ...
- 使用NuGet助您玩转代码生成数据————Entity Framework 之 Code First
[前言] 如果是Code First老鸟或者对Entity Framework不感兴趣,就不用浪费时间往下看了. 记得09年第一次接触ORM————Linq2Sql,从此对她的爱便一发不可收拾,一年后 ...
- Entity Framework 之 Code First
使用NuGet助您玩转代码生成数据————Entity Framework 之 Code First [前言] 如果是Code First老鸟或者对Entity Framework不感兴趣,就不用浪费 ...
- 在Entity Framework 中用 Code First 创建新的数据库
在Entity Framework 中用 Code First 创建新的数据库 (原文链接) 本文将逐步介绍怎样用Code First 创建新数据库,使用在代码中定义类和API中提供的特性(Attri ...
- Entity Framework 6 Code First新特性:支持存储过程
Entity Framework 6提供支持存储过程的新特性,本文具体演示Entity Framework 6 Code First的存储过程操作. Code First的插入/修改/删除存储过程 默 ...
- AppBox升级进行时 - 拥抱Entity Framework的Code First开发模式
AppBox 是基于 FineUI 的通用权限管理框架,包括用户管理.职称管理.部门管理.角色管理.角色权限管理等模块. 从Subsonic到Entity Framework Subsonic最早发布 ...
随机推荐
- 将form表单转化为json数据
参考地址:https://github.com/hongymagic/jQuery.serializeObject
- EF GroupBy 分组 取某条的 总数
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...
- 打开u盘时提示是否要将其格式化的提示
早上打开电脑插入U盘后,发现U盘报以下错误:(心中一紧,昨晚写的文档还在其中呢) 修复方法: Win+R 输入cmd 打开 ,执行命令 chkdsk G: /f 其中G为损坏区域所在盘符,即U盘在电脑 ...
- PLSQL_统计信息系列06_统计信息的历史和日志
20150506 Created By BaoXinjian
- 目的可疑,但方法非常值得学习的书——leo鉴书56
书中提到写作手法绝对值得学习,为此能够打四颗星. 作者是个买直销产品的.靠写字让别人买自己的东西.当中特别强调了卖的多是太空时代的产品,意思就是读者非常可能并不须要,多半是被眼花缭乱的广告词儿骗了 ...
- [转]关于几本模拟IC设计书
1.P.R.Gray的书 这本书被业界誉为模拟IC的Bible,盛名之下,必无虚士.现在已经出到第四版,作者无一例外是业界大牛,该书论述严谨,思路清晰,对电路分析透彻,定义严格明确,无愧Bible之名 ...
- VS2012/13中即将增加InstallShield升级版
对于Visual Studio 2012去掉了前作中的安装程序(Installer)项目模板,许多开发者都感到非常失望.这个流行的项目类型为开发者们提供了若干选项:除了InstallShield LE ...
- 服务器上装filezilla server后,本地的ftp客户端连接不上去
公司一台服务器,上面装了filezilla server后,按平常配置好了,但是在本地用FTP客户端不管怎么连接都连接不上,本地FTP客户端总提示连接失败,远程filezilla server的界面也 ...
- 【Android】7.8 MyDemos项目的结构和主界面相关代码
分类:C#.Android.VS2015: 创建日期:2016-02-17 一.简介 上一讲已经说过,系统升级为Win10后,重新创建了一个新的项目:MyDemos,并把前7章合并到了这个项目中,这次 ...
- uva--242(邮资问题 dp)
输入输出: id=26127" style="color:blue; text-decoration:none">Sample Input 5 2 4 1 4 12 ...