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

目录
  • 一、TPT继承模式
    • 1、Person类
    • 2、使用数据迁移创建数据库
    • 3、填充数据
  • 二、TPH模式
    • 1、创建有继承关系的实体类
    • 2、创建数据上下文
    • 3、使用数据迁移创建数据库
    • 4、不使用默认生成的区别多张表的类型
    • 5、填充数据
    • 6、查询数据
  • 三、TPC模式
    • 1、创建实体类
    • 2、配置数据上下文
    • 3、使用数据迁移生成数据库
    • 4、填充数据

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实体继承模式的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Entity Framework使用Code First模式管理数据库

    一.管理数据库连接 1.使用配置文件管理连接之约定 在数据库上下文类中,如果我们只继承了无参数的DbContext,并且在配置文件中创建了和数据库上下文类同名的连接字符串,那么EF会使用该连接字符串自动计算出数据库的位置和数据库名.比如,我们的数据库上下文定义如下: using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Text; usin

  • EF使用Code First模式生成单数形式表名

    使用Code-First模式生成数据库时,默认生成的数据库表的名称为类型的复数形式,例如实体类名称是"User",默认生成的数据库表名为“Users”,多数情况下我们并不想生成的数据库表名为复数形式,那么应该如何来控制呢? 当我们想要自定义一些数据库表的生成规则的时候,会重写OnModelCreating()方法,控制生成的表名的单复数形式同样可以在这个方法中完成,实现代码如下: using System; using System.Collections.Generic; using

  • CodeFirst从零开始搭建Asp.Net Core2.0网站

    一步步教大家如何搭建Asp.Net Core2.0网站,以下所有都是建立在.NETCore2.0环境已经搭建好 右键解决方案>新建项目> 选择Web>ASP.NETCoreWeb应用程序(.NET Core) 选择Web应用程序,暂时不选择启用Docker,身份验证选择个人用户账户(会自动生成一系列和用户认证的代码) 随后生代码层次目录如下: 其中会包含身份信息的相关实现,比如相关实体信息(user)之类的,如果想对扩展微软自动的生成的用户实体类,可在Models中的Applicatio

  • EF使用Code First模式给实体类添加复合主键

    using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Web; namespace MyFirstMvcApp.Models { /// <summary> /// 登录记录 /// </su

  • C#笔记之EF Code First 数据模型 数据迁移

    目录 一.EF的创建 二.修改数据库 一.加数据库字段 二.加数据库 表 EF的基本使用 一.EF的创建二.修改数据库一.加数据库字段二.加数据库 表 一.EF的创建 第一步: 创建一个类库 第二步: 选择类库 第三步:选择ADO.NET 实体数据模型,名称和你数据库名字对应,我的叫LetDB 第四步:出现了窗体 先解释一下这些EF模型: 1.来自数据库的EF设计器: 先有数据库,根据数据库生成模型 2.空EF设计模型: 模型优先,通过设计的模型生成数据库 3.空 Code First 模型:

  • asp.net mvc CodeFirst模式数据库迁移步骤详解

    利用Code First模式构建好基本的类后,项目也开始搭建完毕并成功运行,而且已经将数据库表结构自动生成了. 但是,我有新的类要加入,有字段需要修改,那怎么办呢,删库,跑路 ? 哈哈 利用数据库迁移,将原有结构不改动,将新建类进行单独建表操作,或者是已有数据库表,改变字段,那就修改表. 迁移步骤: 1.打开程序包管理器控制台:工具->NuGet包管理器->程序包管理器控制台.(当然还有其它方式也可以打开,我比较喜欢这种) 点击后将弹出程序包管理器控制台 极其要注意的是默认项目!!! 2.启动

  • 使用EF的Code First模式操作数据库

    EF的核心程序集位于System.Data.Entity.dll和System.Data.EntityFramework.dll中.支持CodeFirst的位于EntityFramework.dll中.通常使用NuGet Package Manager来添加这些程序集. 如果没有数据库: 1.先写代码,自动创建数据库. 2.如果代码有变化,自动删除数据库重建,或者是使用迁移功能更改已有数据库. 如果已有数据库: 使用EF PowerTools反向工程生成模型. 下面的示例程序中将通过一个控制台程

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

    目录 一.TPT继承模式 1.Person类 2.使用数据迁移创建数据库 3.填充数据 二.TPH模式 1.创建有继承关系的实体类 2.创建数据上下文 3.使用数据迁移创建数据库 4.不使用默认生成的区别多张表的类型 5.填充数据 6.查询数据 三.TPC模式 1.创建实体类 2.配置数据上下文 3.使用数据迁移生成数据库 4.填充数据 Entity Framework的Code First模式有三种实体继承模式 1.Table per Type (TPT)继承 2.Table per Clas

  • Entity Framework使用Code First模式管理视图

    一.什么是视图 视图在RDBMS(关系型数据库管理系统)中扮演了一个重要的角色,它是将多个表的数据联结成一种看起来像是一张表的结构,但是没有提供持久化.因此,可以将视图看成是一个原生表数据顶层的一个抽象.例如,我们可以使用视图提供不同安全的级别,也可以简化必须编写的查询,尤其是我们可以在代码中的多个地方频繁地访问使用视图定义的数据.EF Code First模式现在还不完全支持视图,因此我们必须使用一种变通的方法.这种方法是:将视图真正看成是一张表,让EF定义这张表,然后在删除它,最后再创建一个

  • Entity Framework使用Code First模式管理存储过程

    在EF中使用存储过程和使用视图是很相似的,一般会使用Database对象上的两个方法:SqlQuery和ExecuteSqlCommand.为了从存储过程中读取很多数据行,我们只需要定义一个类,我们会将检索到的所有数据行物质化到该类实例的集合中.比如,从下面的存储过程读取数据: CREATE PROCEDURE [dbo].[SelectBooks] @BookTypeName AS NVARCHAR(10) AS BEGIN select B.Name,B.Author,B.Publicati

  • Entity Framework使用Code First模式管理事务

    一.什么是事务 处理以数据为中心的应用时,另一个重要的话题是事务管理.ADO.NET为事务管理提供了一个非常干净和有效的API.因为EF运行在ADO.NET之上,所以EF可以使用ADO.NET的事务管理功能. 当从数据库角度谈论事务时,它意味着一系列操作被当作一个不可分割的操作.所有的操作要么全部成功,要么全部失败.事务的概念是一个可靠的工作单元,事务中的所有数据库操作应该被看作是一个工作单元. 从应用程序的角度来看,如果我们有多个数据库操作被当作一个工作单元,那么应该将这些操作包裹在一个事务中

  • Entity Framework Core实现软删除与查询过滤器

    注意:我使用的是 Entity Framework Core 2.0 (2.0.0-preview2-final).正式版发布后,功能可能存在变动. 继续探索Entity Framework Core 2.0,今天我将探讨如何轻松使用软删除(或逻辑删除).我的意思是以透明的方式实现软删除,例如,您是物理上的删除行. 要实现软删除,您需要添加一列以指示该行数据是否被逻辑删除.如果您想知道该行被删除,可以使用布尔列,如果您想知道删除的时间,可以使用日期列.其次是更改所有查询,使用此列过滤结果集:您还

  • Entity Framework管理一对一实体关系

    我们现在已经知道如何使用Code First来定义简单的领域类,并且如何使用DbContext类来执行数据库操作.现在我们来看下数据库理论中的多样性关系,我们会使用Code First来实现下面的几种关系: 1.一对一关系: one to one 2.一对多关系: one to many 3.多对多关系::many to many 首先要明确关系的概念.关系就是定义两个或多个对象之间是如何关联的.它是由关系两端的多样性值识别的,比如,一对多意味着在关系的一端,只有一个实体,我们有时称为父母:在关

  • Entity Framework模型优先与实体对象查询

    目录 一.概念: 1.EF6 可实现的功能: 二.安装Entity Framework6 1.VS2019 Installer安装“Entity Framework6 工具” 2.通过Nuget安装“Entity Framework”: 三.新建ObjectContext 和EntityObject 1.“根据模型生成的数据库”的设计器功能实现ModelFirst. 四.实体对象查询:linq to Entities 1.使用lambda表达式查询 2.执行存储过程(通过“添加”-----”函数

  • Entity Framework映射TPH、TPT、TPC与继承类

    目录 一.TPH 1.默认行为 2.Fluent API修改默认行为 二.TPT 1.默认行为 2.Fluent API修改默认行为 三.TPC 1.默认行为 2.Fluent API修改默认行为 四.实体拆分 五.表拆分 六.将类指定为复杂类型 1.指定方法: 2.配置复杂类型的属性 七.DataBase初始化 1.调用Database.SetInitializer方法: 2.通过配置文件更灵活的指定数据库初始化的方式: 3.自定义数据库初始化类 一.TPH Table Per Hierarc

  • Entity Framework代码优先(Code First)模式

    目录 一.Code First 代码优先 二.创建或生成Model代码 1.从数据库生成Model代码 2.手工创建Model代码 三.配置文件 四.操作 1.添加单个实体,Add 2.修改 3.删除,Remove 五.查询 1.Load(): 2.ToList(): 3.Find(): 4.Single().SingleOrDefault().First()等: 六.直接执行SQL语句 1.在实体上运行SQL命令, 2.在Database属性上运行SQL命令 一.Code First 代码优先

随机推荐