Code First的实体继承模式

Entity Framework的Code First模式有三种实体继承模式数据库

一、Table per Type (TPT)继承ide

二、Table per Class Hierarchy(TPH)继承ui

三、Table per Concrete Class (TPC)继承this

1、TPT继承模式url

当领域实体类有继承关系时,TPT继承颇有用,咱们想把这些实体类模型持久化到数据库中,这样,每一个领域实体都会映射到单独的一张表中。这些表会使用一对一关系相互关联,数据库会经过一个共享的主键维护这个关系。spa

假设有这么一个场景:一个组织维护了一个部门工做的全部人的数据库,这些人有些是拿着固定工资的员工,有些是按小时付费的临时工,要持久化这个场景,咱们要建立三个领域实体:Person、Employee和Vendor类。Person类是基类,另外两个类会继承自Person类。实体类结构以下:3d

一、Person类code

复制代码
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 
 7 namespace TPTPattern.Model
 8 {
 9     public class Person
10     {
11         public int Id { get; set; }
12 
13         public string Name { get; set; }
14 
15         public string Email { get; set; }
16 
17         public string PhoneNumber { get; set; }
18     }
19 }
复制代码

Employee类结构对象

复制代码
 1 using System;
 2 using System.Collections.Generic;
 3 using System.ComponentModel.DataAnnotations.Schema;
 4 using System.Linq;
 5 using System.Text;
 6 using System.Threading.Tasks;
 7 
 8 namespace TPTPattern.Model
 9 {
10     [Table("Employee")]
11     public class Employee :Person
12     {
13         /// <summary>
14         /// 薪水
15         /// </summary>
16         public decimal Salary { get; set; }
17     }
18 }
复制代码

Vendor类结构blog

复制代码
 1 using System;
 2 using System.Collections.Generic;
 3 using System.ComponentModel.DataAnnotations.Schema;
 4 using System.Linq;
 5 using System.Text;
 6 using System.Threading.Tasks;
 7 
 8 namespace TPTPattern.Model
 9 {
10     [Table("Vendor")]
11     public class Vendor :Person
12     {
13         /// <summary>
14         /// 每小时的薪水
15         /// </summary>
16         public decimal HourlyRate { get; set; }
17     }
18 }
复制代码

 

 

在VS中的类图以下:

对于Person类,咱们使用EF的默认约定来映射到数据库,而对Employee和Vendor类,咱们使用了数据注解,将它们映射为咱们想要的表名。

而后咱们须要建立本身的数据库上下文类,数据库上下文类定义以下:

复制代码
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Data.Entity;
 4 using System.Linq;
 5 using System.Text;
 6 using System.Threading.Tasks;
 7 using TPTPattern.Model;
 8 
 9 namespace TPTPattern.EFDatabaseContext
10 {
11     public class EFDbContext :DbContext
12     {
13         public EFDbContext()
14             : base("name=Default")
15         { }
16 
17         public DbSet<Person> Persons { get; set; }
18     }
19 }
复制代码

 在上面的上下文中,咱们只添加了实体类Person的DbSet,没有添加另外两个实体类的DbSet。由于其它的两个领域模型都是从这个模型派生的,因此咱们也就至关于将其它两个类添加到了DbSet集合中了,这样EF会使用多态性来使用实际的领域模型。固然,也可使用Fluent API和实体伙伴类来配置映射细节信息。

二、使用数据迁移建立数据库

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

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

三、填充数据

如今咱们使用这些领域实体来建立一个Employee和Vendor类来填充数据,Program类定义以下:

复制代码
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 using TPTPattern.EFDatabaseContext;
 7 using TPTPattern.Model;
 8 
 9 namespace TPTPattern
10 {
11     class Program
12     {
13         static void Main(string[] args)
14         {
15             using (var context = new EFDbContext())
16             {
17                 Employee emp = new Employee() 
18                 {
19                   Name="李白",
20                   Email="LiBai@163.com",
21                    PhoneNumber="18754145782",
22                    Salary=2345m
23                 };
24 
25                 Vendor vendor = new Vendor() 
26                 { 
27                    Name="杜甫",
28                    Email="DuFu@qq.com",
29                    PhoneNumber="18234568123",
30                    HourlyRate=456m
31                 };
32 
33                 context.Persons.Add(emp);
34                 context.Persons.Add(vendor);
35                 context.SaveChanges();
36             }
37 
38             Console.WriteLine("信息录入成功");
39         }
40     }
41 }
复制代码

 查询数据库填充后的数据:

咱们能够看到每一个表都包含单独的数据,这些表之间都有一个共享的主键。于是这些表之间都是一对一的关系。

注:TPT模式主要应用在一对一模式下。

2、TPH模式

当领域实体有继承关系时,可是咱们想未来自全部的实体类的数据保存到单独的一张表中时,TPH继承颇有用。从领域实体的角度,咱们的模型类的继承关系仍然像上面的截图同样:

 

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

注意:从数据库的角度看,这种模式很不优雅,由于咱们将无关的数据保存到了单张表中,咱们的表是不标准的。若是咱们使用这种方法,那么总会存在null值的冗余列。

一、建立有继承关系的实体类

如今咱们建立实体类来实现该继承,注意:此次建立的三个实体类和以前建立的只是没有了类上面的数据注解,这样它们就会映射到数据库的单张表中(EF会默认使用父类的DbSet属性名或复数形式做为表名,而且将派生类的属性映射到那张表中),类结构以下:

 

 

二、建立数据上下文

复制代码
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Data.Entity;
 4 using System.Linq;
 5 using System.Text;
 6 using System.Threading.Tasks;
 7 using TPHPattern.Model;
 8 
 9 namespace TPHPattern.EFDatabaseContext
10 {
11     public class EFDbContext :DbContext
12     {
13         public EFDbContext()
14             : base("name=Default")
15         { }
16 
17         public DbSet<Person> Persons { get; set; }
18 
19         public DbSet<Employee> Employees { get; set; }
20 
21         public DbSet<Vendor> Vendors { get; set; }
22     }
23 }
复制代码

 三、使用数据迁移建立数据库

使用数据迁移生成数据库之后,会发现数据库中只有一张表,并且三个实体类中的字段都在这张表中了, 建立后的数据库表结构以下:

注意:查看生成的表结构,会发现生成的表中多了一个Discriminator字段,它是用来找到记录的实际类型,即从Person表中找到Employee或者Vendor。

四、不使用默认生成的区别多张表的类型

使用Fluent API,修改数据上下文类,修改后的类定义以下:

复制代码
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Data.Entity;
 4 using System.Linq;
 5 using System.Text;
 6 using System.Threading.Tasks;
 7 using TPHPattern.Model;
 8 
 9 namespace TPHPattern.EFDatabaseContext
10 {
11     public class EFDbContext :DbContext
12     {
13         public EFDbContext()
14             : base("name=Default")
15         { }
16 
17         public DbSet<Person> Persons { get; set; }
18 
19         public DbSet<Employee> Employees { get; set; }
20 
21         public DbSet<Vendor> Vendors { get; set; }
22 
23         protected override void OnModelCreating(DbModelBuilder modelBuilder)
24         {
25             // 强制指定PersonType是鉴别器 1表明全职职员 2表明临时工
26             modelBuilder.Entity<Person>()
27                 .Map<Employee>(m => m.Requires("PersonType").HasValue(1))
28                 .Map<Vendor>(m => m.Requires("PersonType").HasValue(2));
29             base.OnModelCreating(modelBuilder);
30         }
31     }
32 }
复制代码

 从新使用数据迁移把实体持久化到数据库,持久化之后的数据库表结构:

生成的PersonType列的类型是int。

五、填充数据

复制代码
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 using TPHPattern.EFDatabaseContext;
 7 using TPHPattern.Model;
 8 
 9 namespace TPHPattern
10 {
11     class Program
12     {
13         static void Main(string[] args)
14         {
15             using (var context = new EFDbContext())
16             {
17                 Employee emp = new Employee()
18                 {
19                     Name = "李白",
20                     Email = "LiBai@163.com",
21                     PhoneNumber = "18754145782",
22                     Salary = 2345m
23                 };
24 
25                 Vendor vendor = new Vendor()
26                 {
27                     Name = "杜甫",
28                     Email = "DuFu@qq.com",
29                     PhoneNumber = "18234568123",
30                     HourlyRate = 456m
31                 };
32 
33                 context.Persons.Add(emp);
34                 context.Persons.Add(vendor);
35                 context.SaveChanges();
36             }
37 
38             Console.WriteLine("信息录入成功");
39         }
40     }
41 }
复制代码

 六、查询数据

注意:TPH模式和TPT模式相比,TPH模式只是少了使用数据注解或者Fluent API配置子类的表名。所以,若是咱们没有在具备继承关系的实体之间提供确切的配置,那么EF会默认将其对待成TPH模式,并把数据放到单张表中。

3、TPC模式

当多个领域实体类派生自一个基类实体,而且咱们想将全部具体类的数据分别保存在各自的表中,以及抽象基类实体在数据库中没有对应的表时,使用TPC继承模式。实体模型仍是和以前的同样。

然而,从数据库的角度看,只有全部具体类所对应的表,而没有抽象类对应的表。生成的数据库以下图:

一、建立实体类

建立领域实体类,这里Person基类应该是抽象的,其余的地方都和上面同样:

二、配置数据上下文

接下来就是应该配置数据库上下文了,若是咱们只在数据库上下文中添加了Person的DbSet泛型属性集合,那么EF会看成TPH继承处理,若是咱们须要实现TPC继承,那么还须要使用Fluent API来配置映射(固然也可使用配置伙伴类),数据库上下文类定义以下:

 

复制代码
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Data.Entity;
 4 using System.Linq;
 5 using System.Text;
 6 using System.Threading.Tasks;
 7 using TPCPattern.Model;
 8 
 9 namespace TPCPattern.EFDatabaseContext
10 {
11     public class EFDbContext :DbContext
12     {
13         public EFDbContext()
14             : base("name=Default")
15         { }
16 
17         public virtual DbSet<Person> Persons { get; set; }
18 
19         protected override void OnModelCreating(DbModelBuilder modelBuilder)
20         {
21             //MapInheritedProperties表示继承以上全部的属性
22             modelBuilder.Entity<Employee>().Map(m => 
23             {
24                 m.MapInheritedProperties();
25                 m.ToTable("Employees");
26             });
27             modelBuilder.Entity<Vendor>().Map(m => 
28             {
29                 m.MapInheritedProperties();
30                 m.ToTable("Vendors");
31             });
32             base.OnModelCreating(modelBuilder);
33         }
34     }
35 }
复制代码

 上面的代码中,MapInheritedProperties方法将继承的属性映射到表中,而后咱们根据不一样的对象类型映射到不一样的表中。

三、使用数据迁移生成数据库

生成的数据库表结构以下:

查看生成的表结构会发现,生成的数据库中只有具体类对应的表,而没有抽象基类对应的表。具体实体类对应的表中有全部抽象基类里面的字段。

四、填充数据

复制代码
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 using TPCPattern.EFDatabaseContext;
 7 using TPCPattern.Model;
 8 
 9 namespace TPCPattern
10 {
11     class Program
12     {
13         static void Main(string[] args)
14         {
15             using (var context = new EFDbContext())
16             {
17                 Employee emp = new Employee()
18                 {
19                     Name = "李白",
20                     Email = "LiBai@163.com",
21                     PhoneNumber = "18754145782",
22                     Salary = 2345m
23                 };
24 
25                 Vendor vendor = new Vendor()
26                 {
27                     Name = "杜甫",
28                     Email = "DuFu@qq.com",
29                     PhoneNumber = "18234568123",
30                     HourlyRate = 456m
31                 };
32 
33                 context.Persons.Add(emp);
34                 context.Persons.Add(vendor);
35                 context.SaveChanges();
36             }
37 
38             Console.WriteLine("信息录入成功");
39         }
40     }
41 }
复制代码

 查询数据库:

 

注意:虽然数据是插入到数据库了,可是运行程序时也出现了异常,异常信息见下图。出现该异常的缘由是EF尝试去访问抽象类中的值,它会找到两个具备相同Id的记录,然而Id列被识别为主键,于是具备相同主键的两条记录就会产生问题。这个异常清楚地代表了存储或者数据库生成的Id列对TPC继承无效。 

若是咱们想使用TPC继承,那么要么使用基于GUID的Id,要么从应用程序中传入Id,或者使用可以维护对多张表自动生成的列的惟一性的某些数据库机制。

相关文章
相关标签/搜索