Entity Framework使用Code First的实体继承模式

 更新时间:2022年3月5日 16:46  点击:239 作者:.NET开发菜鸟

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="18754145782",
                   Salary=2345m
                };

                Vendor vendor = new Vendor()
                {
                   Name="杜甫",
                   Email="DuFu@qq.com",
                   PhoneNumber="18234568123",
                   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(1))
                .Map<Vendor>(m => m.Requires("PersonType").HasValue(2));
            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 = "18754145782",
                    Salary = 2345m
                };

                Vendor vendor = new Vendor()
                {
                    Name = "杜甫",
                    Email = "DuFu@qq.com",
                    PhoneNumber = "18234568123",
                    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 = "18754145782",
                    Salary = 2345m
                };

                Vendor vendor = new Vendor()
                {
                    Name = "杜甫",
                    Email = "DuFu@qq.com",
                    PhoneNumber = "18234568123",
                    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实体继承模式的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持猪先飞。

原文出处:https://www.cnblogs.com/dotnet261010/p/8018266.html

[!--infotagslink--]

相关文章

  • js URLdecode()与urlencode方法支持中文解码

    下面来介绍在js中来利用urlencode对中文编码与接受到数据后利用URLdecode()对编码进行解码,有需要学习的机友可参考参考。 代码如下 复制代码 ...2016-09-20
  • php中json_decode()和json_encode()用法与中文不显示解决办法

    本文章介绍了关于php中json_decode()和json_encode()用法与中文不显示解决办法,有需要的朋友可以参考一下下。 php中json_decode()和json_encode() 1.json_decode(...2016-11-25
  • 源码分析系列之json_encode()如何转化一个对象

    这篇文章主要介绍了源码分析系列之json_encode()如何转化一个对象,对json_encode()感兴趣的同学,可以参考下...2021-04-22
  • VSCode 配置uni-app的方法

    这篇文章主要介绍了VSCode 配置uni-app的方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-07-11
  • vscode安装git及项目开发过程

    这篇文章主要介绍了vscode安装git及项目开发过程,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-05-19
  • Vscode上使用SQL的方法

    这篇文章主要介绍了Vscode上使用SQL的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-01-26
  • PHP json_encode() 函数详解及中文乱码问题

    在 php 中使用 json_encode() 内置函数(php > 5.2)可以使用得 php 中数据可以与其它语言很好的传递并且使用它。这个函数的功能是将数值转换成json数据存储格式。<&#63;php$arr = array ( 'Name'=>'希亚', 'Age'...2015-11-08
  • vscode搭建STM32开发环境的详细过程

    这篇文章主要介绍了vscode搭建STM32开发环境的详细过程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-05-02
  • 微信小程序二维码生成工具 weapp-qrcode详解

    这篇文章主要介绍了微信小程序 二维码生成工具 weapp-qrcode详解,教大家如何在项目中引入weapp-qrcode.js文件,通过实例代码给大家介绍的非常详细,需要的朋友可以参考下...2021-10-23
  • VSCode C++多文件编译的简单使用方法

    这篇文章主要介绍了VSCode C++多文件编译的简单使用方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-03-29
  • VSCode配置C#运行环境的完整步骤

    这篇文章主要给大家介绍了关于VSCode配置C#运行环境的完整步骤,文中通过图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-12-08
  • 浅谈Vue开发人员的7个最好的VSCode扩展

    这篇文章主要介绍了浅谈Vue开发人员的7个最好的VSCode扩展,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-01-20
  • vscode和cmake编译多个C++文件的实现方法

    这篇文章主要介绍了vscode和cmake编译多个C++文件的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-10
  • IntelliJ IDEA 刷题利器 LeetCode 插件详解

    这篇文章主要介绍了IntelliJ IDEA 刷题利器 LeetCode 插件,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-08-21
  • Windows配置VSCode+CMake+Ninja+Boost.Test的C++开发环境(教程详解)

    这篇文章主要介绍了Windows配置VSCode+CMake+Ninja+Boost.Test的C++开发环境,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-05-12
  • 如何使用VSCode配置Rust开发环境(Rust新手教程)

    这篇文章主要介绍了如何使用VSCode配置Rust开发环境(Rust新手教程),本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-07-27
  • VS Code C/C++环境配置教程(无法打开源文件“xxxxxx.h”或者检测到 #include 错误,请更新includePath)(POSIX API)

    这篇文章主要介绍了VS Code C/C++环境配置教程(无法打开源文件“xxxxxx.h” 或者 检测到 #include 错误。请更新includePath) (POSIX API),本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-08-13
  • vscode通过Remote SSH远程连接及离线配置的方法

    这篇文章主要介绍了vscode通过Remote SSH远程连接及离线配置的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-03-16
  • C#利用QrCode.Net生成二维码(Qr码)的方法

    QrCode.Net是一个使用C#编写的用于生成二维码图片的类库,使用它可以非常方便的为WinForm、WebForm、WPF、Silverlight和Windows Phone 7应用程序提供二维码编码输出功能。可以将二维码文件导出为eps格式...2020-06-25
  • .Net(c#)汉字和Unicode编码互相转换实例

    下面小编就为大家带来一篇.Net(c#)汉字和Unicode编码互相转换实例。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25