次の方法で共有


リレーションシップ検出の規則

EF Core では、エンティティ型クラスに基づいてモデルを検出して構築するときに、一連の規則が使用されます。 このドキュメントでは、 エンティティ型間のリレーションシップの検出と構成に使用される規則の概要を示します。

Von Bedeutung

ここで説明する規則は、 マッピング属性 またはモデル構築 API を使用したリレーションシップの明示的な構成によってオーバーライドできます。

ヒント

次のコードは 、RelationshipConventions.csにあります。

ナビゲーションの発見

リレーションシップの検出は、エンティティ型間 のナビゲーションを 検出することから始まります。

参考案内ナビゲーション

エンティティ型のプロパティは、次の場合に 参照ナビゲーション として検出されます。

  • プロパティはパブリックです。
  • プロパティにはゲッターとセッターがあります。
    • セッターはパブリックである必要はありません。プライベートにすることも、他の アクセシビリティを持つ場合もあります。
    • セッターは 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プロパティにセッターがないため、
  • Author.Id Guidはマップされたプリミティブ型であるため、
  • Author.Name'string' はマップされたプリミティブ型であるため、
  • Author.BlogId intはマップされたプリミティブ型であるため、

コレクション ナビゲーション

エンティティ型のプロパティは、次の場合に コレクション ナビゲーション として検出されます。

  • プロパティはパブリックです。
  • プロパティにはゲッターが定義されています。 コレクション ナビゲーションにはセッターを含めることができますが、これは必須ではありません。
  • プロパティ型は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>();
}

ペアリング ナビゲーション

エンティティ型 A からエンティティ型 B に移動するナビゲーションが検出されたら、次に、このナビゲーションが逆方向 (つまり、エンティティ型 B からエンティティ型 A) にあるかどうかを判断する必要があります。このような逆が見つかった場合、2 つのナビゲーションがペアになって、1 つの双方向リレーションシップが形成されます。

リレーションシップの種類は、ナビゲーションとその逆が参照ナビゲーションかコレクション ナビゲーションかによって決まります。 具体的には:

  • 1 つのナビゲーションがコレクション ナビゲーションで、もう一方が参照ナビゲーションである場合、リレーションシップは 一対多になります。
  • 両方のナビゲーションが参照ナビゲーションの場合、リレーションシップは 1 対 1 です。
  • 両方のナビゲーションがコレクション ナビゲーションの場合、リレーションシップは 多対多になります。

これらの種類のリレーションシップの検出を次の例に示します。

BlogPostナビゲーションをペアリングすることで、Blog.PostsPost.Blogの間で 1 対多の 1 つのリレーションシップが検出されます。

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; }
}

BlogAuthorナビゲーションをペアリングすることで、Blog.AuthorAuthor.Blogの間で 1 対 1 のリレーションシップが検出されます。

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; }
}

PostTagナビゲーションをペアリングすることで、Post.TagsTag.Postsの間で 1 つの多対多リレーションシップが検出されます。

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>();
}

2 つのナビゲーションが 2 つの異なる一方向のリレーションシップを表している場合、このナビゲーションのペアリングが正しくない可能性があります。 この場合、2 つのリレーションシップを明示的に構成する必要があります。

リレーションシップのペアリングは、2 つの型の間に 1 つのリレーションシップがある場合にのみ機能します。 2 つの型間の複数のリレーションシップを明示的に構成する必要があります。

ここでは、2 つの異なる型間の関係に関する説明を示します。 ただし、同じ型をリレーションシップの両端に配置できるため、1 つの型で 2 つのナビゲーションを互いにペアにすることができます。 これは自己参照関係と呼ばれます。

外部キー プロパティの検出

リレーションシップのナビゲーションが検出または明示的に構成されると、これらのナビゲーションを使用して、リレーションシップの適切な外部キー プロパティが検出されます。 プロパティは、次の場合に外部キーとして検出されます。

  • プロパティ型は、プリンシパル エンティティ型の主キーまたは代替キーと互換性があります。
    • 型は、同じ場合、または外部キー プロパティの型が主キーまたは代替キー プロパティ型の 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; }
}

一対多ナビゲーションの場合、外部キー プロパティは参照ナビゲーションを含む型に存在する必要があります。これは依存エンティティであるためです。 1 対 1 のリレーションシップの場合、外部キー プロパティの検出は、リレーションシップの依存終了を表す型を決定するために使用されます。 外部キー プロパティが検出されない場合は、 HasForeignKeyを使用して依存側を構成する必要があります。 この例については、 一対一のリレーションシップ を参照してください。

上記の規則は 複合外部キーにも適用されます。複合の各プロパティは、プライマリ キーまたは代替キーの対応するプロパティと互換性のある型を持つ必要があり、各プロパティ名は前述の名前付け規則のいずれかに一致する必要があります。

カーディナリティの決定

EF では、検出されたナビゲーションと外部キーのプロパティを使用して、リレーションシップのカーディナリティとそのプリンシパルおよび依存端を決定します。

  • ペアになっていない参照ナビゲーションがある場合、リレーションシップは一方向の 一対多として構成され、参照ナビゲーションは依存側に設定されます。
  • ペアになっていないコレクション ナビゲーションが 1 つある場合、リレーションシップは一方向の 一対多として構成され、コレクション ナビゲーションはプリンシパル側に設定されます。
  • ペアの参照ナビゲーションとコレクション ナビゲーションがある場合、リレーションシップは双方向 の一対多として構成され、プリンシパル側のコレクション ナビゲーションが使用されます。
  • 参照ナビゲーションが別の参照ナビゲーションとペアになっている場合は、次のようになります。
    • 外部キー プロパティが一方の側で検出されたが、他方では検出されなかった場合、リレーションシップは双方向 の 1 対 1 として構成され、依存側に外部キー プロパティが設定されます。
    • それ以外の場合、依存側を特定できず、EF は依存側を明示的に構成する必要があることを示す例外をスローします。
  • コレクション ナビゲーションが別のコレクション ナビゲーションとペアになっている場合、リレーションシップは双方向 多対多として構成されます。

シャドウ外部キーのプロパティ

EF がリレーションシップの依存終了を決定したが、外部キー プロパティが検出されなかった場合、EF は外部キーを表す シャドウ プロパティ を作成します。 シャドウプロパティ

  • リレーションシップのプリンシパル側で、主キーまたは代替キーのプロパティタイプを持っています。
    • 既定では、この型は 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 非許容であり、結合エンティティとの両方のリレーションシップが必要になります。
    • 連鎖削除規則は、これらのリレーションシップが連鎖削除用に構成されることを意味します。
  • 結合エンティティ型は、2 つの外部キー プロパティで構成される複合主キーで構成されます。 したがって、この例では、主キーは PostsIdTagsIdで構成されています。

これにより、次の 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]);

必要な 1 対 1 のリレーションシップの場合は、一意のインデックスが作成されます。 たとえば、SQLite では次のようになります。

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

または SQL Sever の場合:

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

オプションの 1 対 1 のリレーションシップの場合、SQLite で作成されるインデックスは同じです。

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

ただし、SQL Server では、null 外部キー値をより適切に処理するために、 IS NOT 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> は外部キー プロパティ名のアンダースコアで区切られたリストになります。

その他のリソース