EF Code First 學習筆記:表映射


多個實體映射到一張表

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來區分LodgingResort

使用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