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繼承模式
當領域?qū)嶓w類有繼承關系時,TPT繼承很有用,我們想把這些實體類模型持久化到數(shù)據(jù)庫中,這樣,每個領域?qū)嶓w都會映射到單獨的一張表中。這些表會使用一對一關系相互關聯(lián),數(shù)據(jù)庫會通過一個共享的主鍵維護這個關系。
假設有這么一個場景:一個組織維護了一個部門工作的所有人的數(shù)據(jù)庫,這些人有些是拿著固定工資的員工,有些是按小時付費的臨時工,要持久化這個場景,我們要創(chuàng)建三個領域?qū)嶓w:Person、Employee和Vendor類。Person類是基類,另外兩個類會繼承自Person類。實體類結(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>
/// 每小時的薪水
/// </summary>
public decimal HourlyRate { get; set; }
}
}在VS中的類圖如下:

對于Person類,我們使用EF的默認約定來映射到數(shù)據(jù)庫,而對Employee和Vendor類,我們使用了數(shù)據(jù)注解,將它們映射為我們想要的表名。
然后我們需要創(chuàng)建自己的數(shù)據(jù)庫上下文類,數(shù)據(jù)庫上下文類定義如下:
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會使用多態(tài)性來使用實際的領域模型。當然,也可以使用Fluent API和實體伙伴類來配置映射細節(jié)信息。
2、使用數(shù)據(jù)遷移創(chuàng)建數(shù)據(jù)庫
使用數(shù)據(jù)遷移創(chuàng)建數(shù)據(jù)庫后查看數(shù)據(jù)庫表結(jié)構(gòu):

在TPT繼承中,我們想為每個領域?qū)嶓w類創(chuàng)建單獨的一張表,這些表共享一個主鍵。因此生成的數(shù)據(jù)庫關系圖表如下:

3、填充數(shù)據(jù)
現(xiàn)在我們使用這些領域?qū)嶓w來創(chuàng)建一個Employee和Vendor類來填充數(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ù)庫填充后的數(shù)據(jù):

我們可以看到每個表都包含單獨的數(shù)據(jù),這些表之間都有一個共享的主鍵。因而這些表之間都是一對一的關系。
注:TPT模式主要應用在一對一模式下。
二、TPH模式
當領域?qū)嶓w有繼承關系時,但是我們想將來自所有的實體類的數(shù)據(jù)保存到單獨的一張表中時,TPH繼承很有用。從領域?qū)嶓w的角度,我們的模型類的繼承關系仍然像上面的截圖一樣:

但是從數(shù)據(jù)庫的角度講,應該只有一張表保存數(shù)據(jù)。因此,最終生成的數(shù)據(jù)庫的樣子應該是下面這樣的:

注意:從數(shù)據(jù)庫的角度看,這種模式很不優(yōu)雅,因為我們將無關的數(shù)據(jù)保存到了單張表中,我們的表是不標準的。如果我們使用這種方法,那么總會存在null值的冗余列。
1、創(chuàng)建有繼承關系的實體類
現(xiàn)在我們創(chuàng)建實體類來實現(xiàn)該繼承,注意:這次創(chuàng)建的三個實體類和之前創(chuàng)建的只是沒有了類上面的數(shù)據(jù)注解,這樣它們就會映射到數(shù)據(jù)庫的單張表中(EF會默認使用父類的DbSet屬性名或復數(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ù)庫
使用數(shù)據(jù)遷移生成數(shù)據(jù)庫以后,會發(fā)現(xiàn)數(shù)據(jù)庫中只有一張表,而且三個實體類中的字段都在這張表中了, 創(chuàng)建后的數(shù)據(jù)庫表結(jié)構(gòu)如下:

注意:查看生成的表結(jié)構(gòu),會發(fā)現(xiàn)生成的表中多了一個Discriminator字段,它是用來找到記錄的實際類型,即從Person表中找到Employee或者Vendor。
4、不使用默認生成的區(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)
{
// 強制指定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);
}
}
}重新使用數(shù)據(jù)遷移把實體持久化到數(shù)據(jù)庫,持久化以后的數(shù)據(jù)庫表結(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配置子類的表名。因此,如果我們沒有在具有繼承關系的實體之間提供確切的配置,那么EF會默認將其對待成TPH模式,并把數(shù)據(jù)放到單張表中。
三、TPC模式
當多個領域?qū)嶓w類派生自一個基類實體,并且我們想將所有具體類的數(shù)據(jù)分別保存在各自的表中,以及抽象基類實體在數(shù)據(jù)庫中沒有對應的表時,使用TPC繼承模式。實體模型還是和之前的一樣。
然而,從數(shù)據(jù)庫的角度看,只有所有具體類所對應的表,而沒有抽象類對應的表。生成的數(shù)據(jù)庫如下圖:

1、創(chuàng)建實體類
創(chuàng)建領域?qū)嶓w類,這里Person基類應該是抽象的,其他的地方都和上面一樣:

2、配置數(shù)據(jù)上下文
接下來就是應該配置數(shù)據(jù)庫上下文了,如果我們只在數(shù)據(jù)庫上下文中添加了Person的DbSet泛型屬性集合,那么EF會當作TPH繼承處理,如果我們需要實現(xiàn)TPC繼承,那么還需要使用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 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ù)不同的對象類型映射到不同的表中。
3、使用數(shù)據(jù)遷移生成數(shù)據(jù)庫
生成的數(shù)據(jù)庫表結(jié)構(gòu)如下:

查看生成的表結(jié)構(gòu)會發(fā)現(xiàn),生成的數(shù)據(jù)庫中只有具體類對應的表,而沒有抽象基類對應的表。具體實體類對應的表中有所有抽象基類里面的字段。
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ù)庫:

注意:雖然數(shù)據(jù)是插入到數(shù)據(jù)庫了,但是運行程序時也出現(xiàn)了異常,異常信息見下圖。出現(xiàn)該異常的原因是EF嘗試去訪問抽象類中的值,它會找到兩個具有相同Id的記錄,然而Id列被識別為主鍵,因而具有相同主鍵的兩條記錄就會產(chǎn)生問題。這個異常清楚地表明了存儲或者數(shù)據(jù)庫生成的Id列對TPC繼承無效。
如果我們想使用TPC繼承,那么要么使用基于GUID的Id,要么從應用程序中傳入Id,或者使用能夠維護對多張表自動生成的列的唯一性的某些數(shù)據(jù)庫機制。

到此這篇關于Entity Framework使用Code First實體繼承模式的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
- Entity?Framework代碼優(yōu)先(Code?First)模式
- Entity Framework使用Code First模式管理事務
- Entity Framework使用Code First模式管理存儲過程
- Entity Framework使用Code First模式管理視圖
- Entity Framework使用Code First模式管理數(shù)據(jù)庫
- EF使用Code First模式生成單數(shù)形式表名
- EF使用Code First模式給實體類添加復合主鍵
- 使用EF的Code?First模式操作數(shù)據(jù)庫
- C#筆記之EF Code First 數(shù)據(jù)模型 數(shù)據(jù)遷移
- Entity?Framework代碼優(yōu)先Code?First入門
相關文章
DataGridView使用自定義控件實現(xiàn)簡單分頁功能(推薦)
這篇文章主要介紹了DataGridView使用自定義控件實現(xiàn)簡單分頁功能,數(shù)據(jù)庫使用的是sqlserver,本文通過通過實例代碼給大家講解的非常詳細,需要的朋友參考下吧2019-11-11
asp.net(vb)實現(xiàn)金額轉(zhuǎn)換成大寫的函數(shù)
asp.net(vb)實現(xiàn)金額轉(zhuǎn)換成大寫的函數(shù)代碼,需要的朋友可以參考下。2011-10-10
解決Visual Studio 2005 無法顯示設計視圖的方法
解決Visual Studio 2005 無法顯示設計視圖的方法...2007-04-04
.NET中OpenFileDialog使用線程報錯的解決方法
這篇文章主要為大家詳細介紹了.NET中OpenFileDialog使用線程報錯的解決方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-01-01
Excel、記事本數(shù)據(jù)導入到數(shù)據(jù)庫的實現(xiàn)方法
將手機號批量導入數(shù)據(jù)庫。思路:先將要導入的文件傳上項目里,然后讀取文件的每行數(shù)據(jù)并插入數(shù)據(jù)庫,操作完后再將上傳的文件刪除2013-10-10
Asp.Net使用Bulk實現(xiàn)批量插入數(shù)據(jù)
這篇文章主要介紹了Asp.Net使用Bulk實現(xiàn)批量插入數(shù)據(jù)的方法,對于進行asp.net數(shù)據(jù)庫程序設計非常有借鑒價值,需要的朋友可以參考下2014-09-09
ASP.NET Core MVC如何實現(xiàn)運行時動態(tài)定義Controller類型
這篇文章主要介紹了ASP.NET Core MVC如何實現(xiàn)運行時動態(tài)定義Controller類型,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-06-06

