關聯性中的外鍵和主體索引鍵
所有 一對一 和 一對多 關聯性都是由參考主體端主要或替代索引鍵之相依端的外鍵所定義。 為了方便起見,此主要或替代密鑰稱為關聯性的「主體金鑰」。 多對多 關聯性是由兩個一對多關聯性所組成,每個關聯性都是由參考主體索引鍵的外鍵所定義。
提示
您可以在 ForeignAndPrincipalKeys.cs 中找到下列程式代碼。
外部索引鍵
組成外鍵的屬性或屬性通常 由慣例探索。 您也可以使用 對應屬性 或在 HasForeignKey
模型建置 API 中明確設定屬性。 HasForeignKey
可以搭配 Lambda 運算式使用。 例如,針對由單一屬性組成的外鍵:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey(e => e.ContainingBlogId);
}
或者,針對由多個屬性組成的複合外鍵:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey(e => new { e.ContainingBlogId1, e.ContainingBlogId2 });
}
提示
在模型建置 API 中使用 Lambda 運算式可確保屬性使用可用於程式代碼分析和重構,也為 API 提供屬性類型,以供進一步鏈結的方法使用。
HasForeignKey
也可以傳遞外鍵屬性的名稱做為字串。 例如,針對單一屬性:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey("ContainingBlogId");
}
或者,針對複合外鍵:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey("ContainingBlogId1", "ContainingBlogId2");
}
使用字串在:
- 屬性或屬性是私用的。
- 屬性或屬性不存在於實體類型上,應該建立為 陰影屬性。
- 屬性名稱是根據模型建置程式的一些輸入來計算或建構。
不可為 Null 的外鍵數據行
如選擇性和必要關聯性中所述,外鍵屬性的可為 Null 性會決定關聯性是選擇性還是必要。 不過,可為 Null 的外鍵屬性可用於使用 [Required]
屬性的必要關聯性,或在模型建置 API 中呼叫 IsRequired
。 例如:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey(e => e.BlogId)
.IsRequired();
}
或者,如果依照慣例探索外鍵,IsRequired
則可以在沒有呼叫 HasForeignKey
的情況下使用 :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.IsRequired();
}
最後的結果是,即使外鍵屬性可為 Null,資料庫中的外鍵數據行仍不可為 Null。 您可以視需要明確設定外鍵屬性本身來達成相同的動作。 例如:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.Property(e => e.BlogId)
.IsRequired();
}
陰影外鍵
外鍵屬性可以建立為 陰影屬性。 陰影屬性存在於 EF 模型中,但不存在於 .NET 類型上。 EF 會持續追蹤內部的屬性值和狀態。
當想要從應用程式程式代碼/商業規則所使用的領域模型隱藏外鍵的關係概念時,通常會使用陰影外鍵。 然後,此應用程式程式代碼會透過導覽完全 操作關聯性。
提示
如果要串行化實體,例如透過網路傳送,則當實體不在物件/圖形窗體中時,外鍵值可能會是保持關聯性資訊完好無損的實用方式。 因此,為了達到此目的,將外鍵屬性保留在 .NET 類型中通常是務實的。 外鍵屬性可以是私用的,這通常是很好的妥協,以避免公開外鍵,同時允許其值與實體一起移動。
陰影外鍵屬性通常是 由慣例所建立。 如果的 HasForeignKey
自變數不符合任何 .NET 屬性,也會建立陰影外鍵。 例如:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey("MyBlogId");
}
依照慣例,陰影外鍵會從關聯性中的主體索引鍵取得其類型。 除非偵測到關聯性或設定為必要,否則此類型是可為 Null 的。
您也可以明確建立陰影外鍵屬性,這對於設定屬性的 Facet 很有用。 例如,若要使 屬性不可為 Null:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.Property<string>("MyBlogId")
.IsRequired();
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey("MyBlogId");
}
提示
依照慣例,外鍵屬性會繼承關聯性中主體索引鍵的最大長度和 Unicode 支援等 Facet。 因此,很少需要在外鍵屬性上明確設定Facet。
如果指定的名稱不符合實體類型的任何屬性,則可以使用 ConfigureWarnings
停用陰影屬性的建立。 例如:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.ConfigureWarnings(b => b.Throw(CoreEventId.ShadowPropertyCreated));
外鍵條件約束名稱
依照慣例,外鍵條件約束會命名為 FK_<dependent type name>_<principal type name>_<foreign key property name>
。 針對複合外鍵, <foreign key property name>
會變成外鍵屬性名稱的底線分隔清單。
這可以在使用的模型建置 API HasConstraintName
中變更。 例如:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey(e => e.BlogId)
.HasConstraintName("My_BlogId_Constraint");
}
提示
EF 運行時間不會使用條件約束名稱。 只有在使用 EF Core 移轉建立資料庫架構時,才會使用它。
外鍵的索引
根據慣例,EF 會為外鍵的屬性或屬性建立資料庫索引。 如需慣例所建立之索引類型的詳細資訊,請參閱 模型建置慣例 。
提示
關聯性定義於該模型中所含實體類型之間的EF模型中。 某些關聯性可能需要參考不同內容模型中的實體類型,例如,使用 BoundedContext 模式時。 在這些情況下,外鍵數據行應該對應至一般屬性,然後可以手動操作這些屬性來處理關聯性的變更。
主體金鑰
根據慣例,外鍵會限制在關聯性主體結尾的主鍵。 不過,可以改用替代索引鍵。 這會在 HasPrincipalKey
模型建置 API 上使用 來達成。 例如,針對單一屬性外鍵:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasPrincipalKey(e => e.AlternateId);
}
或者,針對具有多個屬性的複合外鍵:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasPrincipalKey(e => new { e.AlternateId1, e.AlternateId2 });
}
HasPrincipalKey
也可以傳遞替代索引鍵屬性的名稱做為字串。 例如,針對單一屬性索引鍵:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasPrincipalKey("AlternateId");
}
或者,針對複合索引鍵:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasPrincipalKey("AlternateId1", "AlternateId2");
}
注意
主體和外鍵中屬性的順序必須相符。 這也是在資料庫架構中定義索引鍵的順序。 它不一定與實體類型或數據表中的數據行中屬性的順序相同。
不需要呼叫 HasAlternateKey
在主體實體上定義替代索引鍵;當與非主鍵屬性的屬性搭配使用時 HasPrincipalKey
,就會自動完成此動作。 不過, HasAlternateKey
可用於進一步設定替代索引鍵,例如設定其資料庫條件約束名稱。 如需詳細資訊,請參閱 密鑰 。
與無索引鍵實體的關聯性
每個關聯性都必須有參考主體(主要或替代)索引鍵的外鍵。 這表示 無索引鍵實體類型 無法做為關聯性的主體結尾,因為沒有要參考之外鍵的主體索引鍵。
提示
實體類型不能有替代索引鍵,但沒有主鍵。 在此情況下,必須將替代索引鍵 (或其中一個替代索引鍵,如果有數個)升級為主鍵。
不過,無索引鍵實體類型仍然可以定義外鍵,因此可以做為關聯性的相依端。 例如,請考慮這些類型,其中 Tag
沒有索引鍵:
public class Tag
{
public string Text { get; set; } = null!;
public int PostId { get; set; }
public Post Post { get; set; } = null!;
}
public class Post
{
public int Id { get; set; }
}
Tag
可以在關聯性的相依端設定:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Tag>()
.HasNoKey();
modelBuilder.Entity<Post>()
.HasMany<Tag>()
.WithOne(e => e.Post);
}
注意
EF 不支援指向無索引鍵實體類型的導覽。 請參閱 GitHub 問題 #30331。
多對多關聯性的外鍵
在 多對多關聯性中,外鍵是在聯結實體類型上定義,並對應至聯結數據表中的外鍵條件約束。 上述所有專案也可以套用至這些聯結實體外鍵。 例如,設定資料庫條件約束名稱:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.HasMany(e => e.Tags)
.WithMany(e => e.Posts)
.UsingEntity(
l => l.HasOne(typeof(Tag)).WithMany().HasConstraintName("TagForeignKey_Constraint"),
r => r.HasOne(typeof(Post)).WithMany().HasConstraintName("PostForeignKey_Constraint"));
}