运维开发网

首先使用框架代码的实体继承模式

运维开发网 https://www.qedev.com 2022-09-07 19:58 出处:网络
本文详细讲解了Entity?Framework使用Code?First的实体继承模式,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

本文详细讲解了Entity?Framework使用Code?First的实体继承模式,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

实体框架的代码优先模式有三种实体继承模式。

1.每类型表(TPT)继承

2.每类层次结构表(TPH)继承

3.每个具体类的表(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; } }}

员工类别结构

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 { /// lt;summarygt; /// 薪水 /// lt;/summarygt; public decimal Salary { get; set; } }}

供应商类别结构

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 { /// lt;summarygt; /// 每小时的薪水 /// lt;/summarygt; 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 DbSetlt;Persongt; Persons { get; set; } }}

在上面的上下文中,我们只添加了实体类Person的DbSet,而没有添加其他两个实体类的DbSet。因为另外两个域模型都是从这个模型派生出来的,我们相当于把另外两个类添加到DbSet中,所以EF会使用多态来使用实际的域模型。当然,也可以使用Fluent API和实体伙伴类来配置映射细节。


2、使用数据迁移创建数据库

使用数据迁移创建数据库后,查看数据库表结构:


在TPT继承中,我们希望为每个域实体类创建一个单独的表,并且这些表共享一个主键。生成的数据库图表如下:



3、填充数据

现在,我们使用这些域实体创建一个雇员和供应商类来填充数据。程序类定义如下:

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继承是有用的,但是我们希望将所有实体类的数据保存到一个表中。从领域实体的角度来看,我们的模型类的继承关系还是像上面的截图:


但是从数据库的角度来看,应该只有一个表来保存数据。因此,最终生成的数据库应该如下所示:


注意:从数据库的角度来看,这种模式并不优雅,因为我们把不相关的数据保存到单个表中,而且我们的表是不标准的。如果我们使用这种方法,总会有冗余的空值列。


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 DbSetlt;Persongt; Persons { get; set; } public DbSetlt;Employeegt; Employees { get; set; } public DbSetlt;Vendorgt; Vendors { get; set; } }}


3、使用数据迁移创建数据库

使用数据迁移生成数据库后,你会发现数据库中只有一个表,三个实体类中的字段都在这个表中。创建的数据库表结构如下:


注意:查看生成的表结构,会发现生成的表中多了一个鉴别器字段,用来查找记录的实际类型,即从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 DbSetlt;Persongt; Persons { get; set; } public DbSetlt;Employeegt; Employees { get; set; } public DbSetlt;Vendorgt; Vendors { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { // 强制指定PersonType是鉴别器 1代表全职职员 2代表临时工 modelBuilder.Entitylt;Persongt;() .Maplt;Employeegt;(m =gt; m.Requires("PersonType").HasValue(1)) .Maplt;Vendorgt;(m =gt; 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、查询数据


注意:与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 DbSetlt;Persongt; Persons { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { //MapInheritedProperties表示继承以上所有的属性 modelBuilder.Entitylt;Employeegt;().Map(m =gt; { m.MapInheritedProperties(); m.ToTable("Employees"); }); modelBuilder.Entitylt;Vendorgt;().Map(m =gt; { 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,或者使用一些数据库机制来维护为多个表自动生成的列的唯一性。


这就是这篇关于实体框架使用代码优先实体继承模式的文章。希望对大家的学习有所帮助

0

精彩评论

暂无评论...
验证码 换一张
取 消