共用方式為


關聯性探索的慣例

EF Core 會在根據實體類型類別探索和建 置模型 時,使用一組 慣例 。 本檔摘要說明用來探索和設定 實體類型 之間關聯性的慣例。

重要

您可以透過使用 對應屬性 或模型建置 API 明確設定關聯性來覆寫此處所述的慣例。

提示

您可以在 RelationshipConventions.cs 中找到 下列程式碼。

探索導覽

關聯性探索從探索 實體類型之間的流覽 開始。

參考導覽

當下列情況時,會探索實體類型的屬性做為 參考導覽

  • 屬性為 public。
  • 屬性具有 getter 和 setter。
    • setter 不需要為公用;它可以是私人或具有任何其他 協助工具
    • setter 可以是 僅限 Init。
  • 屬性類型是實體類型,也可以是實體類型。 這表示類型
  • 屬性不是靜態的。
  • 屬性不是 索引子屬性

例如,請考慮下列實體類型:

public class Blog
{
    // Not discovered as reference navigations:
    public int Id { get; set; }
    public string Title { get; set; } = null!;
    public Uri? Uri { get; set; }
    public ConsoleKeyInfo ConsoleKeyInfo { get; set; }
    public Author DefaultAuthor => new() { Name = $"Author of the blog {Title}" };

    // Discovered as a reference navigation:
    public Author? Author { get; private set; }
}

public class Author
{
    // Not discovered as reference navigations:
    public Guid Id { get; set; }
    public string Name { get; set; } = null!;
    public int BlogId { get; set; }

    // Discovered as a reference navigation:
    public Blog Blog { get; init; } = null!;
}

針對這些類型, Blog.AuthorAuthor.Blog 探索為參考導覽。 另一方面,不會探索下列屬性 做為參考導覽:

  • Blog.Id,因為 int 是對應的基本類型
  • Blog.Title,因為 'string' 是對應的基本類型
  • Blog.Uri,因為 Uri 會自動轉換成對應的基本類型
  • Blog.ConsoleKeyInfo,因為 ConsoleKeyInfo 是 C# 實值型別
  • Blog.DefaultAuthor,因為 屬性沒有 setter
  • Author.Id,因為 Guid 是對應的基本類型
  • Author.Name,因為 'string' 是對應的基本類型
  • Author.BlogId,因為 int 是對應的基本類型

集合導覽

實體類型的屬性會在下列情況下探索為 集合導覽

  • 屬性為 public。
  • 屬性具有 getter。 集合導覽可以有 setter,但這並非必要專案。
  • 屬性類型是 或 會實作 IEnumerable<TEntity> 、其中 TEntity 是 或 可以是實體類型。 這表示 的類型 TEntity 為 :
  • 屬性不是靜態的。
  • 屬性不是 索引子屬性

例如,在下列程式碼中, Blog.TagsTag.Blogs 都會探索為集合導覽:

public class Blog
{
    public int Id { get; set; }
    public List<Tag> Tags { get; set; } = null!;
}

public class Tag
{
    public Guid Id { get; set; }
    public IEnumerable<Blog> Blogs { get; } = new List<Blog>();
}

配對導覽

例如,探索到實體類型 B 的實體類型 A 到實體類型 B 之後,必須判斷此導覽是否有相反方向的反向巡覽,也就是從實體類型 B 到實體類型 A。如果找到這類反向,則兩個導覽會配對在一起,以形成單一雙向關聯性。

關聯性的類型取決於巡覽及其反向是參考或集合導覽。 具體而言:

  • 如果其中一個導覽是集合導覽,另一個是參考導覽,則關聯性為 一對多
  • 如果這兩個導覽都是參考導覽,則關聯性為 一對一
  • 如果這兩個導覽都是集合導覽,則關聯性是 多對多

下列範例顯示這些關聯性類型的探索:

單一的一對多關聯性會在 和 Post 之間 Blog 探索,方法是配對 Blog.PostsPost.Blog 導覽來探索:

public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    public int Id { get; set; }
    public int? BlogId { get; set; }
    public Blog? Blog { get; set; }
}

在 和 之間 Blog Author 探索到單一對一關聯性,並透過配對 Blog.AuthorAuthor.Blog 流覽來探索:

public class Blog
{
    public int Id { get; set; }
    public Author? Author { get; set; }
}

public class Author
{
    public int Id { get; set; }
    public int? BlogId { get; set; }
    public Blog? Blog { get; set; }
}

在 和 之間 Post Tag 探索單一多對多關聯性,並透過配對 Post.TagsTag.Posts 流覽來探索:

public class Post
{
    public int Id { get; set; }
    public ICollection<Tag> Tags { get; } = new List<Tag>();
}

public class Tag
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>();
}

注意

如果兩個導覽代表兩個不同單向關聯性,則這組導覽可能不正確。 在此情況下,必須明確設定這兩個關聯性。

只有在兩種類型之間有單一關聯性時,才能配對關聯性。 必須明確設定兩種類型之間的多個關聯性。

注意

這裡的描述是兩種不同類型之間的關聯性。 不過,同一類型可能位於關聯性的兩端,因此單一類型可以有兩個導覽彼此配對。 這稱為自我參考關聯性。

探索外鍵屬性

一旦探索或明確設定關聯性的導覽之後,這些導覽會用來探索關聯性的適當外鍵屬性。 當:

  • 屬性類型與主體實體類型上的主要或替代索引鍵相容。
    • 如果類型相同,或外鍵屬性類型是主鍵或替代索引鍵屬性類型的可為 Null 版本,則類型相容。
  • 屬性名稱符合外鍵屬性的其中一個命名慣例。 命名慣例如下:
    • <navigation property name><principal key property name>
    • <navigation property name>Id
    • <principal entity type name><principal key property name>
    • <principal entity type name>Id
  • 此外,如果使用模型建置 API 明確設定相依端,且相依主鍵相容,則相依主鍵也會當做外鍵使用。

提示

「Id」 尾碼可以有任何大小寫。

下列實體類型顯示每個命名慣例的範例。

Post.TheBlogKey 會探索為外鍵,因為它符合模式 <navigation property name><principal key property name>

public class Blog
{
    public int Key { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    public int Id { get; set; }
    public int? TheBlogKey { get; set; }
    public Blog? TheBlog { get; set; }
}

Post.TheBlogID 會探索為外鍵,因為它符合模式 <navigation property name>Id

public class Blog
{
    public int Key { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    public int Id { get; set; }
    public int? TheBlogID { get; set; }
    public Blog? TheBlog { get; set; }
}

Post.BlogKey 會探索為外鍵,因為它符合模式 <principal entity type name><principal key property name>

public class Blog
{
    public int Key { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    public int Id { get; set; }
    public int? BlogKey { get; set; }
    public Blog? TheBlog { get; set; }
}

Post.Blogid 會探索為外鍵,因為它符合模式 <principal entity type name>Id

public class Blog
{
    public int Key { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    public int Id { get; set; }
    public int? Blogid { get; set; }
    public Blog? TheBlog { get; set; }
}

注意

在一對多導覽的情況下,外鍵屬性必須位於具有參考導覽的型別上,因為這將會是相依實體。 在一對一關聯性的情況下,會使用外鍵屬性的探索來判斷哪一種類型代表關聯性的相依結尾。 如果未探索到外鍵屬性,則必須使用 HasForeignKey 來設定相依端。 如需此範例,請參閱 一對一關聯 性。

上述規則也適用于 複合外鍵 ,其中複合的每個屬性都必須具有與主要或替代索引鍵之對應屬性相容的類型,而且每個屬性名稱都必須符合上述其中一種命名慣例。

判斷基數

EF 會使用探索到的導覽和外鍵屬性來判斷關聯性的基數,以及其主體和相依結尾:

  • 如果有一個未配對的參考流覽,則關聯性會設定為單向 一對多 ,並在相依端使用參考導覽。
  • 如果有一個未配對的集合導覽,則關聯性會設定為單 向一對多 ,且集合導覽位於主體端。
  • 如果有配對的參考和集合導覽,則關聯性會設定為雙向 一對多 ,且集合導覽位於主體端。
  • 如果參考導覽與另一個參考導覽配對,則:
    • 如果在一端探索到外鍵屬性,但不是另一端,則關聯性會設定為雙向一對一 ,並在相 依端使用外鍵屬性。
    • 否則,無法判斷相依端,EF 會擲回例外狀況,指出必須明確設定相依專案。
  • 如果集合導覽與另一個集合導覽配對,則關聯性會設定為雙向 多對多

陰影外鍵屬性

如果 EF 已判斷關聯性的相依結尾,但沒有探索到外鍵屬性,則 EF 會建立 陰影屬性 來代表外鍵。 shadow 屬性:

  • 在關聯性主體結尾具有主要或替代索引鍵屬性的類型。
    • 根據預設,類型會設為可為 Null,因此關聯性預設為選擇性。
  • 如果相依端有導覽,則會使用與主要或替代索引鍵屬性名稱串連的導覽名稱來命名陰影外鍵屬性。
  • 如果相依端沒有流覽,則會使用與主要或替代索引鍵屬性名稱串連的主體實體類型名稱來命名陰影外鍵屬性。

串聯刪除

依照慣例,必要的關聯性會設定為 串聯刪除 。 選擇性關聯性設定為不會串聯刪除。

多對多

多對多關聯 性沒有主體和相依性,而且兩端都沒有外鍵屬性。 相反地,多對多關聯性會使用聯結實體類型,其中包含指向多對多兩端的外鍵組。 請考慮下列實體類型,其中會依慣例探索多對多關聯性:

public class Post
{
    public int Id { get; set; }
    public ICollection<Tag> Tags { get; } = new List<Tag>();
}

public class Tag
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>();
}

此探索中使用的慣例如下:

  • 聯結實體類型名為 <left entity type name><right entity type name> 。 因此, PostTag 在此範例中。
    • 聯結資料表的名稱與聯結實體類型相同。
  • 聯結實體類型會針對關聯性的每個方向指定外鍵屬性。 這些名稱為 <navigation name><principal key name> 。 因此,在此範例中,外鍵屬性是 PostsIdTagsId
    • 如果是單向多對多,沒有相關聯導覽的外鍵屬性會命名為 <principal entity type name><principal key name>
  • 外鍵屬性不可為 Null,這兩個關聯性都必須與聯結實體建立關聯性。
    • 串聯刪除慣例表示這些關聯性會設定為串聯刪除。
  • 聯結實體類型是以包含兩個外鍵屬性的複合主鍵所設定。 因此,在此範例中,主鍵是由 和 TagsId 所組成 PostsId

這會導致下列 EF 模型:

Model:
  EntityType: Post
    Properties:
      Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd
    Skip navigations:
      Tags (ICollection<Tag>) CollectionTag Inverse: Posts
    Keys:
      Id PK
  EntityType: Tag
    Properties:
      Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd
    Skip navigations:
      Posts (ICollection<Post>) CollectionPost Inverse: Tags
    Keys:
      Id PK
  EntityType: PostTag (Dictionary<string, object>) CLR Type: Dictionary<string, object>
    Properties:
      PostsId (no field, int) Indexer Required PK FK AfterSave:Throw
      TagsId (no field, int) Indexer Required PK FK Index AfterSave:Throw
    Keys:
      PostsId, TagsId PK
    Foreign keys:
      PostTag (Dictionary<string, object>) {'PostsId'} -> Post {'Id'} Cascade
      PostTag (Dictionary<string, object>) {'TagsId'} -> Tag {'Id'} Cascade
    Indexes:
      TagsId

使用 SQLite 時,會轉譯為下列資料庫架構:

CREATE TABLE "Posts" (
    "Id" INTEGER NOT NULL CONSTRAINT "PK_Posts" PRIMARY KEY AUTOINCREMENT);

CREATE TABLE "Tag" (
    "Id" INTEGER NOT NULL CONSTRAINT "PK_Tag" PRIMARY KEY AUTOINCREMENT);

CREATE TABLE "PostTag" (
    "PostsId" INTEGER NOT NULL,
    "TagsId" INTEGER NOT NULL,
    CONSTRAINT "PK_PostTag" PRIMARY KEY ("PostsId", "TagsId"),
    CONSTRAINT "FK_PostTag_Posts_PostsId" FOREIGN KEY ("PostsId") REFERENCES "Posts" ("Id") ON DELETE CASCADE,
    CONSTRAINT "FK_PostTag_Tag_TagsId" FOREIGN KEY ("TagsId") REFERENCES "Tag" ("Id") ON DELETE CASCADE);

CREATE INDEX "IX_PostTag_TagsId" ON "PostTag" ("TagsId");

索引數

根據慣例,EF 會 為外鍵的屬性或屬性建立資料庫索引 。 建立的索引類型取決於:

  • 關聯性的基數
  • 關聯性是選擇性還是必要
  • 組成外鍵的屬性數目

針對一對多關聯 性,慣例會建立簡單的索引。 系統會針對選擇性和必要關聯性建立相同的索引。 例如,在 SQLite 上:

CREATE INDEX "IX_Post_BlogId" ON "Post" ("BlogId");

或在 SQL Server 上:

CREATE INDEX [IX_Post_BlogId] ON [Post] ([BlogId]);

針對必要的 一對一關聯性 ,會建立唯一索引。 例如,在 SQLite 上:

CREATE UNIQUE INDEX "IX_Author_BlogId" ON "Author" ("BlogId");

或在 SQL Sever 上:

CREATE UNIQUE INDEX [IX_Author_BlogId] ON [Author] ([BlogId]);

針對選擇性的一對一關聯性,在 SQLite 上建立的索引相同:

CREATE UNIQUE INDEX "IX_Author_BlogId" ON "Author" ("BlogId");

不過,在 SQL Server 上, IS NOT NULL 會新增篩選準則,以更好地處理 Null 外鍵值。 例如:

CREATE UNIQUE INDEX [IX_Author_BlogId] ON [Author] ([BlogId]) WHERE [BlogId] IS NOT NULL;

針對複合外鍵,會建立涵蓋所有外鍵資料行的索引。 例如:

CREATE INDEX "IX_Post_ContainingBlogId1_ContainingBlogId2" ON "Post" ("ContainingBlogId1", "ContainingBlogId2");

注意

EF 不會為現有索引或主鍵條件約束所涵蓋的屬性建立索引。

如何停止 EF 建立外鍵的索引

索引會有額外負荷,而且 ,如這裡 所述,可能不一定適合為所有 FK 資料行建立索引。 若要達成此目的, ForeignKeyIndexConvention 可以在建置模型時移除 :

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.Conventions.Remove(typeof(ForeignKeyIndexConvention));
}

如有需要,仍 可針對需要索引的外鍵資料行明確建立 索引。

外鍵條件約束名稱

依照慣例,外鍵條件約束會命名為 FK_<dependent type name>_<principal type name>_<foreign key property name> 。 針對複合外鍵, <foreign key property name> 會變成外鍵屬性名稱的底線分隔清單。

其他資源