1. 程式人生 > >EF Code First 學習筆記:表對映

EF Code First 學習筆記:表對映

原文地址為: 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預設情況不會為表生成字增長。

 


轉載請註明本文地址: EF Code First 學習筆記:表對映