EF Code First 學習筆記:表映射 多個Entity到一張表和一個Entity到多張表


 

 

多個實體映射到一張表

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默認情況不會為表生成字增長。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM