Entity Framwork CodeFirst 學習筆記五:數據庫映射的默認配置和設置


數據庫的映射指的就是對數據庫進行配置,包括生成表名,架構名,列名。這些內容前面的筆記已經涉及到了,還包括的復雜類型的設置,這里就不在贅述。

本次主要學習和掌握如何將單個類映射到多個表中,多個類如何映射到一個通用表中和各種類繼承架構的配置。

讓多個實體映射到同一個表:AKA表切分

通常一個數據庫表中雖然有很多列,但在很多場景只需要使用其中的一部分,其他的只是一些附加的數據。當我們映射一個實體到這樣的表以后,你會發現要浪費資源來處理一些無用的數據。表切分技術可以解決這個問題,這種技術允許在一個單獨表中訪問多個實體。

為了將多個實體映射到一個通用的表中,實體必須遵循如下規則:

  • 實體必須是一對一關系
  • 實體必須共享一個通用鍵

看一下書中的例子:

   [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; }        
        public PersonPhoto Photo { get; set; }
    }
    
    [Table("People")]
    public class PersonPhoto
    {
        //共享通用鍵
        [Key, ForeignKey("PhotoOf")]
        public int PersonId { get; set; }
        [Column(TypeName="image")]
        public byte[] Photo { get; set; }
        public string Caption { get; set; }
        public Person PhotoOf { get; set; }
    }

 兩類的確滿足我們上面的關系,我們在程序運行的時候插入一條數據:

 static void Main(string[] args)
 {
    Database.SetInitializer(new DropCreateDatabaseIfModelChanges<LxContext>());
    InsertPerson();
 }
 private static void InsertPerson()
 {
    PersonPhoto ph = new PersonPhoto() { Caption = "個人照片", Photo = new byte[8] };
    //可以插入成功
    Person p1 = new Person() { FirstName = "Jhon", LastName = "Micheal", SocialSecurityNumber = 123, Photo = ph };
    using (var context = new LxContext())
    {
       context.Persons.Add(p1);
       context.SaveChanges();
    }
 }

 數據庫生成后如下圖:

注意:當我們這樣做的時候,當我們插入數據的時候,兩個類必須都擁有數據。否則會產生“遇到了無效數據。缺少必要的關系” 的異常!

 將一個單獨的實體映射到多個表

翻轉一下上面的例子,我們可以把 一個單獨的實體映射的到多個表中,這種稱之為實體分割。實現這個功能 不能使用Data Annotations ,因為Data Annotations 沒有子屬性的概念。

我們再增加一個類:

    //將PersonInfo 分割成兩個表
    public class PersonInfo
    {
        [Key]
        public int PersonId { get; set; }
        public int SocialSecurityNumber { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public byte[] Photo { get; set; }
        public string Caption { get; set; }
    }

 然后用FluntAPI進行配置:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
   modelBuilder.Entity<PersonInfo>().Map(m =>
   {
     m.ToTable("t_PersonInfo");
     m.Properties(p => p.FirstName);
     m.Properties(p => p.LastName);                
     m.Properties(p => p.SocialSecurityNumber);
   }).Map(m =>
          {
            m.ToTable("t_PersonPhoto");
            m.Properties(p => p.Photo);
            m.Properties(p => p.Caption);
          });
}

  結果如下圖:

我們發現PersonInfo這個類的確被我們拆分成了兩個表,t_PersonInfo 和 t_PersonPhoto,而且還進行了外鍵關聯。

但這里有幾個需要注意的地方:
1、雖然生成了兩表,但是只有t_PersonInfo 的主鍵是自增的,t_PersonPhoto 的主鍵 PersonId 是非自增的,並且是外鍵關聯到了 t_PersonInfo的主鍵
2、雖然建立了外鍵但是不會建立級聯刪除。
3、在使用FluntAPI 進行實體分割的時候,務必不要跳過任何屬性。否則Code First還會自動的創建第三張表,保存那些你遺漏的屬性。

雖然沒有建立級聯刪除,但是 EF框架知道,如果對t_PersonInfo 操作,它必須建立一個跨越兩個表的執行命令,我們來實驗一下:

增加一個Person

var p2 = new PersonInfo
{
    FirstName = "Monroe",
    LastName = "Marilyn",
    SocialSecurityNumber = 456
};
using (var context = new LxContext())
{
    context.PersonInfos.Add(p2);
    context.SaveChanges();
}

我們利用Sql Profiler監視,會發現數據庫執行了兩條SQL分別是:

exec sp_executesql N'insert [dbo].[t_PersonInfo]([SocialSecurityNumber], [FirstName], [LastName])
values (@0, @1, @2)
select [PersonId]
from [dbo].[t_PersonInfo]
where @@ROWCOUNT > 0 and [PersonId] = scope_identity()',N'@0 int,@1 nvarchar(max) ,@2 nvarchar(max) ',@0=456,@1=N'Monroe',@2=N'Marilyn'

exec sp_executesql N'insert [dbo].[t_PersonPhoto]([PersonId], [Photo], [Caption])
values (@0, null, null)
',N'@0 int',@0=1

 修改一個Person:

using (var context = new LxContext())
{
    var PersonList = context.PersonInfos.ToList();
    var p = PersonList[0];
    p.Caption = "my photo";
    p.Photo = new byte[8];
    context.SaveChanges();
}

  監視執行的SQL 為:

--查詢
SELECT
[Extent1].[PersonId] AS [PersonId], [Extent2].[SocialSecurityNumber] AS [SocialSecurityNumber], [Extent2].[FirstName] AS [FirstName], [Extent2].[LastName] AS [LastName], [Extent1].[Photo] AS [Photo], [Extent1].[Caption] AS [Caption] FROM [dbo].[t_PersonPhoto] AS [Extent1] INNER JOIN [dbo].[t_PersonInfo] AS [Extent2] ON [Extent1].[PersonId] = [Extent2].[PersonId] --修改 exec sp_executesql N'update [dbo].[t_PersonPhoto] set [Photo] = @0, [Caption] = @1 where ([PersonId] = @2) ',N'@0 varbinary(max) ,@1 nvarchar(max) ,@2 int',@0=0x0000000000000000,@1=N'my photo',@2=1

 刪除Person:

using (var context = new LxContext())
{
     var PersonList = context.PersonInfos.ToList();
     var p = PersonList[0];
     context.PersonInfos.Remove(p);
     context.SaveChanges();
}

 監視生成的SQL為:

--查詢
SELECT 
[Extent1].[PersonId] AS [PersonId], 
[Extent2].[SocialSecurityNumber] AS [SocialSecurityNumber], 
[Extent2].[FirstName] AS [FirstName], 
[Extent2].[LastName] AS [LastName], 
[Extent1].[Photo] AS [Photo], 
[Extent1].[Caption] AS [Caption]
FROM  [dbo].[t_PersonPhoto] AS [Extent1]
INNER JOIN [dbo].[t_PersonInfo] AS [Extent2] ON [Extent1].[PersonId] = [Extent2].[PersonId]

--刪除
exec sp_executesql N'delete [dbo].[t_PersonPhoto]
where ([PersonId] = @0)',N'@0 int',@0=1
exec sp_executesql N'delete [dbo].[t_PersonInfo]
where ([PersonId] = @0)',N'@0 int',@0=1

 通過這些操作,我們已經知道雖然沒有生成級聯刪除或者修改,但是EF框架會知道如何執行,不用我們去手動管理。

類繼承的映射與配置:

EF框架支持各種模型中的繼承層次結構。無論你使用Code First,Model First還是Database First來定義模型都不用擔心繼承的類型問題,也不用考慮EF框架如何使用這些類型進行查詢,跟蹤變更和更新數據。

TPH(Table Per Hierarchy):

TPH:基類和派生類都映射到同一張表中,通過使用鑒別列來識別是否為子類型。這是Code First默認規則使用的表映射方法。

public class Pet
{
    public int PetId { get; set; }        
    public string PetName { get; set; }
 }

public class Dog : Pet
{
    public int Age { get; set; }
    public string Des { get; set; }
}

 然后插入一些數據:

var pet = new Pet
{
     PetName = "兔寶寶",
};
 var dog = new Dog
{
   PetName="狗寶寶",
   Age=1,
   Des="1歲狗寶寶"
};
using (var context = new LxContext()) { context.Pets.Add(pet); context.Pets.Add(dog); context.SaveChanges(); }

 運行程序會看到生成的表結構和插入的數據:

我們會發現,生成了一個表,其列名就是父類和子類屬性的和,還有一鑒別列 Discriminator,看數據我們就能知道,鑒別列的數值是通過類名來進行區分的,Pet :為父類數據,Dog:為子類數據。

以上是默認的配置規則,當然也可以利用FluntAPI方式進行配置,修改鑒別列的列名和值,EF 會自動識別值的類型。

modelBuilder.Entity<Pet>().Map(m =>
{
     m.ToTable("t_Pet");
     m.Requires("IsFather").HasValue(true);
}).Map<Dog>(m =>
{
     m.Requires("IsFather").HasValue(false);
});

TPT(Table Per Type):

TPH將所有層次的類都放在了一個表里,而TPT在一個單獨的表中儲存來自基類的屬性,在派生類定義的附加屬性儲存在另一個表里,並使用外鍵與主表相連接。

我們只需要為派生類指定表名即可,可以使用Data Annotations 也可以使用Fluent API來完成這項工作。

DataAnnotation方式:

[Table("t_Pet")]
public class Pet
{
   public int PetId { get; set; }        
   public string PetName { get; set; }
}
[Table("t_Dog")]
public class Dog : Pet
{
   public int Age { get; set; }
   public string Des { get; set; }
}

 結果如下:

FluntAPI 方式: 

modelBuilder.Entity<Pet>().ToTable("t_Pet");
modelBuilder.Entity<Dog>().ToTable("t_Dog");

 也可以更具體指定關系:

 modelBuilder.Entity<Pet>().Map(m =>
 {
       m.ToTable("t_Pet");
  }).Map<Dog>(m =>
 {
       m.ToTable("t_Dog");
 });

 TPC(Table Per Concrete Type)

TPC類似TPT,基類與派生類都映射在不同的表中,不同的是派生類中還包括了基類的字段。TPC只能用Fluent API來配置。

modelBuilder.Entity<Pet>()
.Map(m => {
         m.ToTable("t_Pet");
       })
.Map<Dog>(m => {
          m.ToTable("t_Dog");
          m.MapInheritedProperties();
});

生成表結構如下:

雖然生成了表,但是兩個表的主鍵PetId 均不是自動增長,我們插入數據的時候,需要指定PetId:

var pet = new Pet
{
      PetId=1,
      PetName = "兔寶寶",
 };

//雖然生成了兩個表,但是PetId 不能=1,否則出現重復鍵異常
 var dog = new Dog
 {
       PetId=2,
       PetName="狗寶寶",
       Age=1,
       Des="1歲狗寶寶"
};
 using (var context = new LxContext())
{
       context.Pets.Add(pet);
       context.Pets.Add(dog);
       context.SaveChanges();
 }

 結果如下:

注意:TPH 、TPT、TPC 三種種方式的 其他數據操作比如查詢,刪除,修改均沒有試驗,需要待考察!

--=源碼下載=--


免責聲明!

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



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