您好,登錄后才能下訂單哦!
這篇文章主要介紹Entity Framework如何使用Code First的實體繼承模式,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
Entity Framework的Code First模式有三種實體繼承模式
1、Table per Type (TPT)繼承
2、Table per Class Hierarchy(TPH)繼承
3、Table per Concrete Class (TPC)繼承
當領域實體類有繼承關系時,TPT繼承很有用,我們想把這些實體類模型持久化到數據庫中,這樣,每個領域實體都會映射到單獨的一張表中。這些表會使用一對一關系相互關聯,數據庫會通過一個共享的主鍵維護這個關系。
假設有這么一個場景:一個組織維護了一個部門工作的所有人的數據庫,這些人有些是拿著固定工資的員工,有些是按小時付費的臨時工,要持久化這個場景,我們要創建三個領域實體:Person、Employee和Vendor類。Person類是基類,另外兩個類會繼承自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和實體伙伴類來配置映射細節信息。
使用數據遷移創建數據庫后查看數據庫表結構:
在TPT繼承中,我們想為每個領域實體類創建單獨的一張表,這些表共享一個主鍵。因此生成的數據庫關系圖表如下:
現在我們使用這些領域實體來創建一個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繼承很有用。從領域實體的角度,我們的模型類的繼承關系仍然像上面的截圖一樣:
但是從數據庫的角度講,應該只有一張表保存數據。因此,最終生成的數據庫的樣子應該是下面這樣的:
注意:從數據庫的角度看,這種模式很不優雅,因為我們將無關的數據保存到了單張表中,我們的表是不標準的。如果我們使用這種方法,那么總會存在null值的冗余列。
現在我們創建實體類來實現該繼承,注意:這次創建的三個實體類和之前創建的只是沒有了類上面的數據注解,這樣它們就會映射到數據庫的單張表中(EF會默認使用父類的DbSet屬性名或復數形式作為表名,并且將派生類的屬性映射到那張表中),類結構如下:
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; } } }
使用數據遷移生成數據庫以后,會發現數據庫中只有一張表,而且三個實體類中的字段都在這張表中了, 創建后的數據庫表結構如下:
注意:查看生成的表結構,會發現生成的表中多了一個Discriminator字段,它是用來找到記錄的實際類型,即從Person表中找到Employee或者Vendor。
使用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。
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("信息錄入成功"); } } }
注意:TPH模式和TPT模式相比,TPH模式只是少了使用數據注解或者Fluent API配置子類的表名。因此,如果我們沒有在具有繼承關系的實體之間提供確切的配置,那么EF會默認將其對待成TPH模式,并把數據放到單張表中。
當多個領域實體類派生自一個基類實體,并且我們想將所有具體類的數據分別保存在各自的表中,以及抽象基類實體在數據庫中沒有對應的表時,使用TPC繼承模式。實體模型還是和之前的一樣。
然而,從數據庫的角度看,只有所有具體類所對應的表,而沒有抽象類對應的表。生成的數據庫如下圖:
創建領域實體類,這里Person基類應該是抽象的,其他的地方都和上面一樣:
接下來就是應該配置數據庫上下文了,如果我們只在數據庫上下文中添加了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方法將繼承的屬性映射到表中,然后我們根據不同的對象類型映射到不同的表中。
生成的數據庫表結構如下:
查看生成的表結構會發現,生成的數據庫中只有具體類對應的表,而沒有抽象基類對應的表。具體實體類對應的表中有所有抽象基類里面的字段。
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的實體繼承模式”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。