多個實體映射到一張表
Code First允許將多個實體映射到同一張表上,實體必須遵循如下規則:
- 實體必須是一對一關系
- 實體必須共享一個公共鍵
觀察下面兩個實體:
public class Person { [Key] public int PersonId { get; set; } public int SocialSecurityNumber { get; set; } public string FirstName { get; set; } public string LastName { get; set; } [Timestamp] public byte[] RowVersion { get; set; } public PersonPhoto Photo { get; set; } } public class PersonPhoto { [Key, ForeignKey("PhotoOf")] public int PersonId { get; set; } public byte[] Photo { get; set; } public string Caption { get; set; } public Person PhotoOf { get; set; } }
它們之間是一對一的關系,並且主鍵數據類型相同,所以我們可以將它們映射到同數據庫的同一個表中,只需指定表名即可:
[Table("People")] public class Person [Table("People")] public class PersonPhoto
PS:我按照上面的模型映射,但生成數據庫的時候會報錯:
實體類型“PersonPhoto”和“Person”無法共享表“People”,因為它們不在同一類型層次結構中,或者它們之間的匹配的主鍵沒有有效的一對一外鍵關系。
然后我又把模型改了一下:
[Table("People")] public class Person { [Key, ForeignKey("Photo")] public int PersonId { get; set; } public int SocialSecurityNumber { get; set; } public string FirstName { get; set; } public string LastName { get; set; } [Timestamp] public byte[] RowVersion { get; set; } public PersonPhoto Photo { get; set; } } [Table("People")] public class PersonPhoto { [Key, ForeignKey("PhotoOf")] public int PersonId { get; set; } public byte[] Photo { get; set; } public string Caption { get; set; } public Person PhotoOf { get; set; } }
映射可以成功,成功映射后的表結構如圖:
但是在插入數據的時候Person類中的Photo屬性不能為空,否則會報錯:
遇到了無效數據。缺少必要的關系。請檢查 StateEntries 以確定違反約束的源。
PersonPhoto ph = new PersonPhoto() { Caption = "個人照片",Photo=new byte[8]}; //可以插入成功 Person p1 = new Person() { FirstName = "Jhon", LastName = "Micheal",SocialSecurityNumber=123,Photo=ph}; //沒有給Photo賦值,插入失敗 Person p2 = new Person() { FirstName = "Jhon", LastName = "Micheal",SocialSecurityNumber=123};
將一個實體映射到多張表
現在我們反轉一下,將一個實體映射到多張表,這可以用Fluent API的Map方法來實現,不能使用使用Data Annotations ,因為Data Annotations 沒有屬性子集的概念。
我們就將People表映射到數據庫中A,B兩表
public class PersonInfo { [Key] public int PersonId { get; set; } public int SocialSecurityNumber { get; set; } public string FirstName { get; set; } public string LastName { get; set; } [Timestamp] public byte[] RowVersion { get; set; } public byte[] Photo { get; set; } public string Caption { get; set; } }
modelBuilder.Entity<PersonInfo>().Map(m => { m.ToTable("A"); m.Properties(p => p.FirstName); m.Properties(p => p.LastName); m.Properties(p => p.RowVersion); m.Properties(p => p.SocialSecurityNumber); }).Map(m => { m.ToTable("B"); m.Properties(p => p.Photo); m.Properties(p => p.Caption); });
生成的表結構如圖:
可以看到,Code First自動的為這兩張表創建了主鍵和外鍵。在生成的表中,只有主表(A表)的主鍵是自增長的。
注意:用Map映射的時候務必不要跳過任何屬性!不然Code First還會自動的創建第三張表,保存那些你遺漏的屬性。
上面的PersonInfo11就是Code First自動創建的第三張表,因為我Map的時候遺漏了SocialSecurityNumber屬性。
繼承類的映射
TPH(Table Per Hierarchy)
TPH:基類和派生類都映射到同一張表中,通過使用鑒別列來識別是否為子類型。這是Code First默認規則使用的表映射方法。
public class Lodging { public int LodgingId { get; set; } [Required] [MaxLength(200)] [MinLength(10)] public string Name { get; set; } public string Owner { get; set; } public decimal MilesFromNearestAirport { get; set; } public int DestinationId { get; set; } } public class Resort : Lodging { public string Entertainment { get; set; } public string Activities { get; set; } }
生成的數據結構如圖:
所以的屬性都映射到同一張表中,包括派生類中的Entertainment,Activities,而且還多了一列:Discriminator。EF正是通過這一列來識別數據來源。 我們可以插入數據測試一下:
var lodging = new Lodging { Name = "Rainy Day Motel", }; var resort = new Resort { Name = "Top Notch Resort and Spa", MilesFromNearestAirport = 30, Activities = "Spa, Hiking, Skiing, Ballooning", }; using (var context = new BreakAwayContext()) { context.Lodgings.Add(lodging); context.Lodgings.Add(resort); context.SaveChanges(); }
可以看到EF通過Discriminator來區分Lodging與Resort。
使用Fluent API定制TPH區分符字段
如果你覺得默認的鑒別列(discriminator)列名不夠直觀的話,我們可以通過Fluent API來配置discriminator列的類型和命名(Data Annotations 沒有標記可用於定制TPH)。
modelBuilder.Entity<Lodging>().Map(m => { m.ToTable("Lodgings"); m.Requires("LodgingType").HasValue("Standard"); }).Map<Resort>(m => { m.Requires("LodgingType").HasValue("Resort"); });
Requires的參數即是你要的列名,HasValue用來指定鑒別列中的值。
如果基類只有一個派生類,我們也可以將鑒別列的數據類型設置為bool值:
modelBuilder.Entity<Lodging>().Map(m => { m.ToTable("Lodging"); m.Requires("IsResort").HasValue(false); }) .Map<Resort>(m => { m.Requires("IsResort").HasValue(true); });
TPT(Table Per Type)
TPH將所有層次的類都放在了一個表里,而TPT在一個單獨的表中儲存來自基類的屬性,在派生類定義的附加屬性儲存在另一個表里,並使用外鍵與主表相連接。
我們顯示的指定派生類生成的表名即可:
[Table("Resorts")] public class Resort : Lodging { public string Entertainment { get; set; } public string Activities { get; set; } }
我們可以看到生成了兩張表,模型中的派生類Resort映射的表中只包括它自已的兩個屬性:Entertainment、Activities,基類映射的表中也只包含它自己的屬性。並且Resorts中LodgingId即是主鍵也作為外鍵關聯到表Lodgings.
TPC(Table Per Concrete Type)
TPC類似TPT,基類與派生類都映射在不同的表中,不同的是派生類中還包括了基類的字段。TPC只能用Fluent API來配置。
modelBuilder.Entity<Lodging>().Map(m => { m.ToTable("Lodgings"); }).Map<Resort>(m => { m.ToTable("Resorts"); m.MapInheritedProperties(); });
生成的數據庫中派生類Resort映射的表中Resorts也包括了Lodging中的字段。
PS:不知道為什么,發現TPC模式,EF默認情況不會為表生成字增長。