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,或者使用可以維護對多張表自動生成的列的惟一性的某些數據庫機制。

相關文章
相關標籤/搜索