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的实体继承模式的更多相关文章

  1. Code First的实体继承模式

    Entity Framework的Code First模式有三种实体继承模式 1.Table per Type (TPT)继承 2.Table per Class Hierarchy(TPH)继承 3 ...

  2. Entity Framework(EF) Code First将实体中的string属性映射成text类型的几种方式

    1.通过ColumnType属性设置 [Column(TypeName="text")] public string Text { get; set; } 在进行以上属性设置时,请 ...

  3. 【极力分享】[C#/.NET]Entity Framework(EF) Code First 多对多关系的实体增,删,改,查操作全程详细示例【转载自https://segmentfault.com/a/1190000004152660】

      [C#/.NET]Entity Framework(EF) Code First 多对多关系的实体增,删,改,查操作全程详细示例 本文我们来学习一下在Entity Framework中使用Cont ...

  4. [C#/.NET]Entity Framework(EF) Code First 多对多关系的实体增,删,改,查操作全程详细示例

    本文我们来学习一下在Entity Framework中使用Context删除多对多关系的实体是如何来实现的.我们将以一个具体的控制台小实例来了解和学习整个实现Entity Framework 多对多关 ...

  5. 使用NuGet助您玩转代码生成数据————Entity Framework 之 Code First

    [前言] 如果是Code First老鸟或者对Entity Framework不感兴趣,就不用浪费时间往下看了. 记得09年第一次接触ORM————Linq2Sql,从此对她的爱便一发不可收拾,一年后 ...

  6. Entity Framework 之 Code First

    使用NuGet助您玩转代码生成数据————Entity Framework 之 Code First [前言] 如果是Code First老鸟或者对Entity Framework不感兴趣,就不用浪费 ...

  7. 在Entity Framework 中用 Code First 创建新的数据库

    在Entity Framework 中用 Code First 创建新的数据库 (原文链接) 本文将逐步介绍怎样用Code First 创建新数据库,使用在代码中定义类和API中提供的特性(Attri ...

  8. Entity Framework 6 Code First新特性:支持存储过程

    Entity Framework 6提供支持存储过程的新特性,本文具体演示Entity Framework 6 Code First的存储过程操作. Code First的插入/修改/删除存储过程 默 ...

  9. AppBox升级进行时 - 拥抱Entity Framework的Code First开发模式

    AppBox 是基于 FineUI 的通用权限管理框架,包括用户管理.职称管理.部门管理.角色管理.角色权限管理等模块. 从Subsonic到Entity Framework Subsonic最早发布 ...

随机推荐

  1. 使用SecureCRT连接linux

    1.登录之后进入linux系统,输入ifconfig(interfaces config)查看网卡信息 2.设置VMWare的虚拟机连接方式为仅主机模式 3.查看VMWare为仅主机模式虚拟网卡IP地 ...

  2. 在 Asp.NET MVC 中使用 SignalR 实现推送功能 [转]

    在 Asp.NET MVC 中使用 SignalR 实现推送功能 罗朝辉 ( http://blog.csdn.net/kesalin ) CC许可,转载请注明出处 一,简介 Signal 是微软支持 ...

  3. Report_Report Builder的一些基本概念(概念)

    2014-05-31 Created By BaoXinjian

  4. eclipse引入的第三方jar包放到同一个目录下

    相信大家对这个不陌生吧: 使用eclipse,在JAVA项目中导入第三方jar包,然后看到一长串引入jar包信息,如下图: 看着不美观,也不是非常有必要,能不能像图中JRE System Librar ...

  5. TensorFlow运作方式入门

    TensorFlow运作方式入门 代码:tensorflow/g3doc/tutorials/mnist/ 本篇教程的目的,是向大家展示如何利用TensorFlow使用(经典)MNIST数据集训练并评 ...

  6. Lattice系列FPGA入门相关0(Lattice与Altera、Xilinx对比及入门)

    需求说明:Lattice系统FPGA入门 内容       :Lattice与Altera.Xilinx对比 来自       :时间的诗 1.Lattice与Altera.Xilinx对比 latt ...

  7. Java并发:volatile内存可见性和指令重排

    volatile两大作用 1.保证内存可见性 2.防止指令重排 此外需注意volatile并不保证操作的原子性. (一)内存可见性 1 概念 JVM内存模型:主内存和线程独立的工作内存 Java内存模 ...

  8. SourceInsight-显示文件完整路径

    使用Source insight的时候想看文件的全路径,但是默认的是中间省略的路径,所以可以通过: 1.Options-->Preferences-->Display 设置Trim lon ...

  9. python random模块(获取随机数)的常用方法及示例

    random.randomrandom.random()用于生成一个0到1的随机符点数: 0 <= n < 1.0 random.uniformrandom.uniform(a, b),用 ...

  10. 08、通过自定义依赖属性,用 StateTrigger 修改全局主题样式

    在 Win 10 的 UWP 中,需要在 xaml 中,通过使用 StateTrigger 修改全局的文本大小.Background.画刷等依赖属性等主题样式.下面只针对字体大小进行描述,其它依赖属性 ...