Entity?Framework使用Code?First的實(shí)體繼承模式
Entity Framework的Code First模式有三種實(shí)體繼承模式
1、Table per Type (TPT)繼承
2、Table per Class Hierarchy(TPH)繼承
3、Table per Concrete Class (TPC)繼承
一、TPT繼承模式
當(dāng)領(lǐng)域?qū)嶓w類有繼承關(guān)系時(shí),TPT繼承很有用,我們想把這些實(shí)體類模型持久化到數(shù)據(jù)庫(kù)中,這樣,每個(gè)領(lǐng)域?qū)嶓w都會(huì)映射到單獨(dú)的一張表中。這些表會(huì)使用一對(duì)一關(guān)系相互關(guān)聯(lián),數(shù)據(jù)庫(kù)會(huì)通過(guò)一個(gè)共享的主鍵維護(hù)這個(gè)關(guān)系。
假設(shè)有這么一個(gè)場(chǎng)景:一個(gè)組織維護(hù)了一個(gè)部門(mén)工作的所有人的數(shù)據(jù)庫(kù),這些人有些是拿著固定工資的員工,有些是按小時(shí)付費(fèi)的臨時(shí)工,要持久化這個(gè)場(chǎng)景,我們要?jiǎng)?chuàng)建三個(gè)領(lǐng)域?qū)嶓w:Person、Employee和Vendor類。Person類是基類,另外兩個(gè)類會(huì)繼承自Person類。實(shí)體類結(jié)構(gòu)如下:
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類結(jié)構(gòu)
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類結(jié)構(gòu)
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> /// 每小時(shí)的薪水 /// </summary> public decimal HourlyRate { get; set; } } }
在VS中的類圖如下:
對(duì)于Person類,我們使用EF的默認(rèn)約定來(lái)映射到數(shù)據(jù)庫(kù),而對(duì)Employee和Vendor類,我們使用了數(shù)據(jù)注解,將它們映射為我們想要的表名。
然后我們需要?jiǎng)?chuàng)建自己的數(shù)據(jù)庫(kù)上下文類,數(shù)據(jù)庫(kù)上下文類定義如下:
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; } } }
在上面的上下文中,我們只添加了實(shí)體類Person的DbSet,沒(méi)有添加另外兩個(gè)實(shí)體類的DbSet。因?yàn)槠渌膬蓚€(gè)領(lǐng)域模型都是從這個(gè)模型派生的,所以我們也就相當(dāng)于將其它兩個(gè)類添加到了DbSet集合中了,這樣EF會(huì)使用多態(tài)性來(lái)使用實(shí)際的領(lǐng)域模型。當(dāng)然,也可以使用Fluent API和實(shí)體伙伴類來(lái)配置映射細(xì)節(jié)信息。
2、使用數(shù)據(jù)遷移創(chuàng)建數(shù)據(jù)庫(kù)
使用數(shù)據(jù)遷移創(chuàng)建數(shù)據(jù)庫(kù)后查看數(shù)據(jù)庫(kù)表結(jié)構(gòu):
在TPT繼承中,我們想為每個(gè)領(lǐng)域?qū)嶓w類創(chuàng)建單獨(dú)的一張表,這些表共享一個(gè)主鍵。因此生成的數(shù)據(jù)庫(kù)關(guān)系圖表如下:
3、填充數(shù)據(jù)
現(xiàn)在我們使用這些領(lǐng)域?qū)嶓w來(lái)創(chuàng)建一個(gè)Employee和Vendor類來(lái)填充數(shù)據(jù),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("信息錄入成功"); } } }
查詢數(shù)據(jù)庫(kù)填充后的數(shù)據(jù):
我們可以看到每個(gè)表都包含單獨(dú)的數(shù)據(jù),這些表之間都有一個(gè)共享的主鍵。因而這些表之間都是一對(duì)一的關(guān)系。
注:TPT模式主要應(yīng)用在一對(duì)一模式下。
二、TPH模式
當(dāng)領(lǐng)域?qū)嶓w有繼承關(guān)系時(shí),但是我們想將來(lái)自所有的實(shí)體類的數(shù)據(jù)保存到單獨(dú)的一張表中時(shí),TPH繼承很有用。從領(lǐng)域?qū)嶓w的角度,我們的模型類的繼承關(guān)系仍然像上面的截圖一樣:
但是從數(shù)據(jù)庫(kù)的角度講,應(yīng)該只有一張表保存數(shù)據(jù)。因此,最終生成的數(shù)據(jù)庫(kù)的樣子應(yīng)該是下面這樣的:
注意:從數(shù)據(jù)庫(kù)的角度看,這種模式很不優(yōu)雅,因?yàn)槲覀儗o(wú)關(guān)的數(shù)據(jù)保存到了單張表中,我們的表是不標(biāo)準(zhǔn)的。如果我們使用這種方法,那么總會(huì)存在null值的冗余列。
1、創(chuàng)建有繼承關(guān)系的實(shí)體類
現(xiàn)在我們創(chuàng)建實(shí)體類來(lái)實(shí)現(xiàn)該繼承,注意:這次創(chuàng)建的三個(gè)實(shí)體類和之前創(chuàng)建的只是沒(méi)有了類上面的數(shù)據(jù)注解,這樣它們就會(huì)映射到數(shù)據(jù)庫(kù)的單張表中(EF會(huì)默認(rèn)使用父類的DbSet屬性名或復(fù)數(shù)形式作為表名,并且將派生類的屬性映射到那張表中),類結(jié)構(gòu)如下:
2、創(chuàng)建數(shù)據(jù)上下文
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、使用數(shù)據(jù)遷移創(chuàng)建數(shù)據(jù)庫(kù)
使用數(shù)據(jù)遷移生成數(shù)據(jù)庫(kù)以后,會(huì)發(fā)現(xiàn)數(shù)據(jù)庫(kù)中只有一張表,而且三個(gè)實(shí)體類中的字段都在這張表中了, 創(chuàng)建后的數(shù)據(jù)庫(kù)表結(jié)構(gòu)如下:
注意:查看生成的表結(jié)構(gòu),會(huì)發(fā)現(xiàn)生成的表中多了一個(gè)Discriminator字段,它是用來(lái)找到記錄的實(shí)際類型,即從Person表中找到Employee或者Vendor。
4、不使用默認(rèn)生成的區(qū)別多張表的類型
使用Fluent API,修改數(shù)據(jù)上下文類,修改后的類定義如下:
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) { // 強(qiáng)制指定PersonType是鑒別器 1代表全職職員 2代表臨時(shí)工 modelBuilder.Entity<Person>() .Map<Employee>(m => m.Requires("PersonType").HasValue(1)) .Map<Vendor>(m => m.Requires("PersonType").HasValue(2)); base.OnModelCreating(modelBuilder); } } }
重新使用數(shù)據(jù)遷移把實(shí)體持久化到數(shù)據(jù)庫(kù),持久化以后的數(shù)據(jù)庫(kù)表結(jié)構(gòu):
生成的PersonType列的類型是int。
5、填充數(shù)據(jù)
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、查詢數(shù)據(jù)
注意:TPH模式和TPT模式相比,TPH模式只是少了使用數(shù)據(jù)注解或者Fluent API配置子類的表名。因此,如果我們沒(méi)有在具有繼承關(guān)系的實(shí)體之間提供確切的配置,那么EF會(huì)默認(rèn)將其對(duì)待成TPH模式,并把數(shù)據(jù)放到單張表中。
三、TPC模式
當(dāng)多個(gè)領(lǐng)域?qū)嶓w類派生自一個(gè)基類實(shí)體,并且我們想將所有具體類的數(shù)據(jù)分別保存在各自的表中,以及抽象基類實(shí)體在數(shù)據(jù)庫(kù)中沒(méi)有對(duì)應(yīng)的表時(shí),使用TPC繼承模式。實(shí)體模型還是和之前的一樣。
然而,從數(shù)據(jù)庫(kù)的角度看,只有所有具體類所對(duì)應(yīng)的表,而沒(méi)有抽象類對(duì)應(yīng)的表。生成的數(shù)據(jù)庫(kù)如下圖:
1、創(chuàng)建實(shí)體類
創(chuàng)建領(lǐng)域?qū)嶓w類,這里Person基類應(yīng)該是抽象的,其他的地方都和上面一樣:
2、配置數(shù)據(jù)上下文
接下來(lái)就是應(yīng)該配置數(shù)據(jù)庫(kù)上下文了,如果我們只在數(shù)據(jù)庫(kù)上下文中添加了Person的DbSet泛型屬性集合,那么EF會(huì)當(dāng)作TPH繼承處理,如果我們需要實(shí)現(xiàn)TPC繼承,那么還需要使用Fluent API來(lái)配置映射(當(dāng)然也可以使用配置伙伴類),數(shù)據(jù)庫(kù)上下文類定義如下:
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方法將繼承的屬性映射到表中,然后我們根據(jù)不同的對(duì)象類型映射到不同的表中。
3、使用數(shù)據(jù)遷移生成數(shù)據(jù)庫(kù)
生成的數(shù)據(jù)庫(kù)表結(jié)構(gòu)如下:
查看生成的表結(jié)構(gòu)會(huì)發(fā)現(xiàn),生成的數(shù)據(jù)庫(kù)中只有具體類對(duì)應(yīng)的表,而沒(méi)有抽象基類對(duì)應(yīng)的表。具體實(shí)體類對(duì)應(yīng)的表中有所有抽象基類里面的字段。
4、填充數(shù)據(jù)
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("信息錄入成功"); } } }
查詢數(shù)據(jù)庫(kù):
注意:雖然數(shù)據(jù)是插入到數(shù)據(jù)庫(kù)了,但是運(yùn)行程序時(shí)也出現(xiàn)了異常,異常信息見(jiàn)下圖。出現(xiàn)該異常的原因是EF嘗試去訪問(wèn)抽象類中的值,它會(huì)找到兩個(gè)具有相同Id的記錄,然而Id列被識(shí)別為主鍵,因而具有相同主鍵的兩條記錄就會(huì)產(chǎn)生問(wèn)題。這個(gè)異常清楚地表明了存儲(chǔ)或者數(shù)據(jù)庫(kù)生成的Id列對(duì)TPC繼承無(wú)效。
如果我們想使用TPC繼承,那么要么使用基于GUID的Id,要么從應(yīng)用程序中傳入Id,或者使用能夠維護(hù)對(duì)多張表自動(dòng)生成的列的唯一性的某些數(shù)據(jù)庫(kù)機(jī)制。
到此這篇關(guān)于Entity Framework使用Code First實(shí)體繼承模式的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Entity?Framework代碼優(yōu)先(Code?First)模式
- Entity Framework使用Code First模式管理事務(wù)
- Entity Framework使用Code First模式管理存儲(chǔ)過(guò)程
- Entity Framework使用Code First模式管理視圖
- Entity Framework使用Code First模式管理數(shù)據(jù)庫(kù)
- EF使用Code First模式生成單數(shù)形式表名
- EF使用Code First模式給實(shí)體類添加復(fù)合主鍵
- 使用EF的Code?First模式操作數(shù)據(jù)庫(kù)
- C#筆記之EF Code First 數(shù)據(jù)模型 數(shù)據(jù)遷移
- Entity?Framework代碼優(yōu)先Code?First入門(mén)
相關(guān)文章
.Net Web Api中利用FluentValidate進(jìn)行參數(shù)驗(yàn)證的方法
最近在做Web API,用到了流式驗(yàn)證,就簡(jiǎn)單的說(shuō)說(shuō)這個(gè)流式驗(yàn)證,下面這篇文章主要給大家介紹了關(guān)于.Net Web Api中利用FluentValidate進(jìn)行參數(shù)驗(yàn)證的相關(guān)資料,,需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-07-07詳解ASP.NET Core 2.0 路由引擎之網(wǎng)址生成(譯)
這篇文章主要介紹了詳解ASP.NET Core 2.0 路由引擎之網(wǎng)址生成(譯),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-11-11WPF實(shí)現(xiàn)簡(jiǎn)單的跑馬燈效果
這篇文章主要為大家詳細(xì)介紹了WPF實(shí)現(xiàn)簡(jiǎn)單的跑馬燈效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06如何在ASP.NET Core類庫(kù)項(xiàng)目中讀取配置文件詳解
這篇文章主要給大家介紹了關(guān)于如何在ASP.NET Core類庫(kù)項(xiàng)目中讀取配置文件的相關(guān)資料,這是朋友提的一個(gè)問(wèn)題,文中通過(guò)示例代碼介紹的非常詳解,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起看看吧。2017-10-10在ASP.NET Core5.0中訪問(wèn)HttpContext的方法步驟
這篇文章主要介紹了在ASP.NET Core5.0中訪問(wèn)HttpContext的方法步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11aspnet_regsql.exe 工具注冊(cè)數(shù)據(jù)庫(kù)的圖文方法
自 ASP.NET 2.0 起,微軟在 ASP.NET 上新增了很多功能,其中包括 Membership , Role , Profile 等等諸多功能2010-03-03