リレーションシップ

リレーションシップは、2 つのエンティティが互いを関連付ける方法を定義します。 リレーショナル データベースでは、これは外部キー制約によって表されます。

注意

この記事のほとんどのサンプルでは、概念を示す一対多リレーションシップを使用します。 一対一および多対多リレーションシップの例については、記事の最後にある「その他のリレーションシップ パターン」セクションを参照してください。

用語の定義

リレーションシップの説明に使用される用語は多数あります

  • 依存エンティティ: これは、外部キー プロパティを含むエンティティです。 リレーションシップの "子" と呼ばれる場合があります。

  • プリンシパル エンティティ: これは、主または代替キーのプロパティを含むエンティティです。 リレーションシップの "親" と呼ばれる場合があります。

  • プリンシパル キー: プリンシパル エンティティを一意に識別するプロパティです。 主キーまたは代替キーの場合があります。

  • 外部キー: 関連エンティティのプリンシパル キー値を格納するために使用される依存エンティティのプロパティです。

  • ナビゲーション プロパティ: 関連エンティティを参照するプリンシパル エンティティまたは依存エンティティに対して定義されたプロパティです。

    • コレクション ナビゲーション プロパティ: 多くの関連エンティティへの参照を含むナビゲーション プロパティです。

    • 参照ナビゲーション プロパティ: 1 つの関連エンティティへの参照を保持するナビゲーション プロパティです。

    • 逆ナビゲーション プロパティ: 特定のナビゲーション プロパティについて説明する場合、この用語はリレーションシップのもう一方の端にあるナビゲーション プロパティを指します。

  • 自己参照リレーションシップ: 依存エンティティ型とプリンシパル エンティティ型が同じリレーションシップ。

次のコードは、BlogPost の間の一対多の関係を示しています。

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}
  • Post は依存エンティティです。

  • Blog はプリンシパル エンティティです

  • Blog.BlogId はプリンシパル キーです (この場合は、代替キーではなく主キーです)

  • Post.BlogId は外部キーです

  • Post.Blog は参照ナビゲーション プロパティです

  • Blog.Posts はコレクション ナビゲーション プロパティです

  • Post.BlogBlog.Posts の逆ナビゲーション プロパティ (またはその逆) です

規約

既定では、型で検出されたナビゲーション プロパティがある場合、リレーションシップが作成されます。 指す型を現在のデータベース プロバイダーによってスカラー型としてマップできない場合、プロパティはナビゲーション プロパティと見なされます。

注意

規約によって検出されるリレーションシップは、常にプリンシパル エンティティの主キーを対象とします。 代替キーを対象とするには、新しい Fluent API を使用して追加の構成を行う必要がります。

完全に定義されたリレーションシップ

リレーションシップの最も一般的なパターンは、リレーションシップの両端でナビゲーション プロパティを定義し、依存エンティティ クラスで外部キー プロパティを定義します。

  • 2 つの型の間にナビゲーション プロパティのペアが見つかった場合、それらは同じリレーションシップの逆ナビゲーション プロパティとして構成されます。

  • 依存エンティティに、これらのパターンの 1 つと一致する名前を持つプロパティが含まれている場合は、外部キーとして構成されます。

    • <navigation property name><principal key property name>
    • <navigation property name>Id
    • <principal entity name><principal key property name>
    • <principal entity name>Id
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}

この例では、強調表示されているプロパティを使用してリレーションシップを構成します。

注意

プロパティが主キーである場合、またはプリンシパル キーと互換性がない型の場合は、外部キーとして構成されません。

外部キーのないプロパティ

依存エンティティ クラスでは外部キー プロパティを定義することが推奨されますが、必須ではありません。 外部キー プロパティが見つからない場合は、シャドウ外部キー プロパティ<navigation property name><principal key property name> という名前で、または、依存型にナビゲーションが存在しない場合は <principal entity name><principal key property name> という名前で導入されます。

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public Blog Blog { get; set; }
}

この例では、ナビゲーション名を前に追加すると冗長になるため、シャドウ外部キーは BlogId です。

注意

同じ名前のプロパティが既に存在する場合は、シャドウ プロパティ名に数値のサフィックスが付けられます。

単一ナビゲーション プロパティ

ナビゲーション プロパティを 1 つ含むだけ (逆ナビゲーションなし、外部キー プロパティなし) で、規約で定義されたリレーションシップを持つには十分です。 また、1 つのナビゲーション プロパティと外部キー プロパティを持つ場合もあります。

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
}

制限事項

2 つの型 (つまり、互いにポイントする 2 組以上のナビゲーション) の間で複数のナビゲーション プロパティが定義されている場合、ナビゲーション プロパティによって表されるリレーションシップがあいまいになります。 あいまいさを解決するには、手動でこれらを構成する必要があります。

連鎖削除

規約により、カスケード削除は必須のリレーションシップの場合は Cascade に設定され、オプションのリレーションシップの場合は ClientSetNull に設定されます。 Cascade は、依存エンティティも削除されることを意味します。 ClientSetNull は、メモリに読み込まれない依存エンティティは変更されないままであり、手動で削除するか、有効なプリンシパル エンティティをポイントするように更新する必要があることを意味します。 メモリに読み込まれるエンティティの場合、EF Core によって外部キーのプロパティの null への設定が試行されます。

必須とオプションのリレーションシップの違いについては、「必須およびオプションのリレーションシップ」セクションを参照してください。

さまざまな削除の動作と、規約で使用される既定値の詳細については、「カスケード削除」を参照してください。

手動構成

Fluent API でリレーションシップを構成するには、まず、リレーションシップを構成するナビゲーション プロパティを識別します。 HasOne または HasMany により、構成を開始するエンティティ型のナビゲーション プロパティが特定されます。 次に、WithOne または WithMany への呼び出しをチェーンして、逆ナビゲーションを特定します。 HasOne/WithOne は参照ナビゲーション プロパティに使用され、HasMany/WithMany は、コレクション ナビゲーション プロパティに使用されます。

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>()
            .HasOne(p => p.Blog)
            .WithMany(b => b.Posts);
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public Blog Blog { get; set; }
}

単一ナビゲーション プロパティ

ナビゲーション プロパティが 1 つしかない場合は、WithOneWithMany のパラメーターなしオーバーロードがあります。 これは、概念上はリレーションシップのもう一方の端に参照またはコレクションがありますが、エンティティ クラスに含まれるナビゲーション プロパティはないことを示します。

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .HasMany(b => b.Posts)
            .WithOne();
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
}

ナビゲーション プロパティの構成

注意

この機能は EF Core 5.0 で導入されました。

ナビゲーション プロパティが作成された後、さらに構成が必要な場合があります。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(b => b.Posts)
        .WithOne();

    modelBuilder.Entity<Blog>()
        .Navigation(b => b.Posts)
        .UsePropertyAccessMode(PropertyAccessMode.Property);
}

注意

この呼び出しは、ナビゲーション プロパティの作成に使用することはできません。 これは、以前にリレーションシップを定義したことで、または規約から作成されたナビゲーション プロパティを構成するためにのみ使用されます。

外部キー

Fluent API を使用して、特定のリレーションシップの外部キー プロパティとして使用する必要があるプロパティを構成できます。

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>()
            .HasOne(p => p.Blog)
            .WithMany(b => b.Posts)
            .HasForeignKey(p => p.BlogForeignKey);
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int BlogForeignKey { get; set; }
    public Blog Blog { get; set; }
}

シャドウ外部キー

文字列オーバーロード HasForeignKey(...) を使用して、シャドウ プロパティを外部キーとして構成できます (詳細については、「 シャドウ プロパティ 」を参照してください)。 次に示すように、外部キーとして使用する前に、シャドウ プロパティをモデルに明示的に追加することをお勧めします。

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Add the shadow property to the model
        modelBuilder.Entity<Post>()
            .Property<int>("BlogForeignKey");

        // Use the shadow property as a foreign key
        modelBuilder.Entity<Post>()
            .HasOne(p => p.Blog)
            .WithMany(b => b.Posts)
            .HasForeignKey("BlogForeignKey");
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public Blog Blog { get; set; }
}

外部キー制約の名前

規約により、リレーショナル データベースを対象とする場合、外部キー制約には、次の名前が付けられます。FK_<依存型名>_<プリンシパル型名>_<外部キープロパティ名>。 複合外部キーの場合、<外部キー プロパティ名>は、アンダースコアで区切られた外部キー プロパティ名の一覧になります。

制約名は次のように構成することもできます。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasOne(p => p.Blog)
        .WithMany(b => b.Posts)
        .HasForeignKey(p => p.BlogId)
        .HasConstraintName("ForeignKey_Post_Blog");
}

ナビゲーション プロパティなし

ナビゲーション プロパティは、必ずしも指定する必要はありません。 単に、リレーションシップの一方の側に外部キーを指定することができます。

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>()
            .HasOne<Blog>()
            .WithMany()
            .HasForeignKey(p => p.BlogId);
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int BlogId { get; set; }
}

プリンシパル キー

外部キーで主キー以外のプロパティを参照する場合は、Fluent API を使用してリレーションシップのプリンシパル キー プロパティを構成できます。 プリンシパル キーとして構成したプロパティは、自動的に代替キーとして設定されます。

internal class MyContext : DbContext
{
    public DbSet<Car> Cars { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<RecordOfSale>()
            .HasOne(s => s.Car)
            .WithMany(c => c.SaleHistory)
            .HasForeignKey(s => s.CarLicensePlate)
            .HasPrincipalKey(c => c.LicensePlate);
    }
}

public class Car
{
    public int CarId { get; set; }
    public string LicensePlate { get; set; }
    public string Make { get; set; }
    public string Model { get; set; }

    public List<RecordOfSale> SaleHistory { get; set; }
}

public class RecordOfSale
{
    public int RecordOfSaleId { get; set; }
    public DateTime DateSold { get; set; }
    public decimal Price { get; set; }

    public string CarLicensePlate { get; set; }
    public Car Car { get; set; }
}

必須およびオプションのリレーションシップ

Fluent API を使用して、リレーションシップが必須かオプションかを構成できます。 これは最終的に、外部キープロパティが必須かオプションを制御します。 これは、シャドウ状態の外部キーを使用している場合に最も役立ちます。 エンティティ クラスに外部キー プロパティがある場合、リレーションシップの必要性は、外部キープロパティが必須かオプションかに基づいて決定されます (詳細については、「必須およびオプションのプロパティ」を参照してください)。

外部キー プロパティは依存エンティティ型にあるため、必須であると構成された場合は、すべての依存エンティティに対応するプリンシパル エンティティが必要であることを意味します。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasOne(p => p.Blog)
        .WithMany(b => b.Posts)
        .IsRequired();
}

注意

特に構成されていない限り、IsRequired(false) への呼び出しでも、外部キー プロパティがオプションになります。

連鎖削除

Fluent API を使用して、特定のリレーションシップに対してカスケード削除の動作を明示的に構成することができます。

各オプションの詳細な説明については、「カスケード削除」を参照してください。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasOne(p => p.Blog)
        .WithMany(b => b.Posts)
        .OnDelete(DeleteBehavior.Cascade);
}

その他のリレーションシップ パターン

一対一

一対一のリレーションシップには、両側に参照ナビゲーション プロパティがあります。 これらは一対多リレーションシップと同じ規約に従いますが、確実に 1 つの依存関係だけが各プリンシパルに関連付けられるように、外部キー プロパティに一意のインデックスが導入されます。

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public BlogImage BlogImage { get; set; }
}

public class BlogImage
{
    public int BlogImageId { get; set; }
    public byte[] Image { get; set; }
    public string Caption { get; set; }

    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}

注意

EF では、外部キー プロパティを検出する機能に基づいて、依存するエンティティの 1 つが選択されます。 間違ったエンティティが依存関係として選択されている場合は、Fluent API を使用して修正できます。

Fluent API を使ってリレーションシップを構成する場合は、HasOneWithOne メソッドを使用します。

外部キーを構成するときは、依存エンティティ型を指定する必要があります。以下の一覧で、HasForeignKey に指定されているジェネリック パラメーターに注目してください。 一対多リレーションシップでは、参照ナビゲーションを持つエンティティが依存であり、コレクションを持つエンティティがプリンシパルであることが明らかです。 しかし、一対一リレーションシップではそうではないため、明示的に定義する必要があります。

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<BlogImage> BlogImages { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .HasOne(b => b.BlogImage)
            .WithOne(i => i.Blog)
            .HasForeignKey<BlogImage>(b => b.BlogForeignKey);
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public BlogImage BlogImage { get; set; }
}

public class BlogImage
{
    public int BlogImageId { get; set; }
    public byte[] Image { get; set; }
    public string Caption { get; set; }

    public int BlogForeignKey { get; set; }
    public Blog Blog { get; set; }
}

既定では、依存側はオプションと見なされますが、必要に応じて構成することができます。 ただし、依存エンティティが指定されたかどうかは EF によって検証されません。そのため、この構成は、データベース マッピングで適用が許可されている場合にのみ違いが生じます。 この一般的なシナリオは、既定でテーブル分割を使用する参照所有型です。

modelBuilder.Entity<Order>(
    ob =>
    {
        ob.OwnsOne(
            o => o.ShippingAddress,
            sa =>
            {
                sa.Property(p => p.Street).IsRequired();
                sa.Property(p => p.City).IsRequired();
            });

        ob.Navigation(o => o.ShippingAddress)
            .IsRequired();
    });

この構成では、ShippingAddress に対応する列は、データベースで null 非許容としてマークされます。

注意

null 非許容の参照型を使用している場合は、IsRequired の呼び出しは必要ありません。

注意

依存関係が必須かどうかを構成する機能は、EF Core 5.0 で導入されました。

多対多

多対多リレーションシップでは、両方の側にコレクション ナビゲーション プロパティが必要です。 これらは、他の種類のリレーションシップと同様に規約によって検出されます。

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public ICollection<Tag> Tags { get; set; }
}

public class Tag
{
    public string TagId { get; set; }

    public ICollection<Post> Posts { get; set; }
}

このリレーションシップをデータベースで実装する方法は、PostTag の両方への外部キーを含む結合テーブルによって行われます。 たとえば、次に示すのは、上記のモデルのリレーショナル データベースに EF によって作成されるものです。

CREATE TABLE [Posts] (
    [PostId] int NOT NULL IDENTITY,
    [Title] nvarchar(max) NULL,
    [Content] nvarchar(max) NULL,
    CONSTRAINT [PK_Posts] PRIMARY KEY ([PostId])
);

CREATE TABLE [Tags] (
    [TagId] nvarchar(450) NOT NULL,
    CONSTRAINT [PK_Tags] PRIMARY KEY ([TagId])
);

CREATE TABLE [PostTag] (
    [PostsId] int NOT NULL,
    [TagsId] nvarchar(450) NOT NULL,
    CONSTRAINT [PK_PostTag] PRIMARY KEY ([PostsId], [TagsId]),
    CONSTRAINT [FK_PostTag_Posts_PostsId] FOREIGN KEY ([PostsId]) REFERENCES [Posts] ([PostId]) ON DELETE CASCADE,
    CONSTRAINT [FK_PostTag_Tags_TagsId] FOREIGN KEY ([TagsId]) REFERENCES [Tags] ([TagId]) ON DELETE CASCADE
);

内部的には、EF により、結合エンティティ型と呼ばれる結合テーブルを表すエンティティ型を作成します。 Dictionary<string, object> は、現在、外部キー プロパティの任意の組み合わせを処理するために使用されています。詳細については、「プロパティ バッグのエンティティ型」を参照してください。 モデルには、複数の多対多リレーションシップが存在する可能性があるため、結合エンティティ型には一意の名前を指定する必要があります (ここでは PostTag)。 これを可能にする機能は、共有型のエンティティ型と呼ばれます。

重要

規約により結合エンティティ型に使用されている CLR 型は、パフォーマンスを向上させるために将来のリリースで変更される可能性があります。 次のセクションで説明するように、明示的に構成されている場合を除いて、結合の種類が Dictionary<string, object> であることに依存しないでください。

多対多ナビゲーションは、結合エンティティ型を効果的にスキップするため、スキップ ナビゲーションと呼ばれます。 一括構成を使用している場合は、すべてのスキップ ナビゲーションを取得 GetSkipNavigationsできます。

foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
    foreach (var skipNavigation in entityType.GetSkipNavigations())
    {
        Console.WriteLine(entityType.DisplayName() + "." + skipNavigation.Name);
    }
}

結合エンティティ型の構成

結合エンティティ型には構成を適用するのが一般的です。 このアクションは、UsingEntity を使用して完了できます。

modelBuilder
    .Entity<Post>()
    .HasMany(p => p.Tags)
    .WithMany(p => p.Posts)
    .UsingEntity(j => j.ToTable("PostTags"));

モデル シード データ は、匿名型を使用して、結合エンティティ型に指定できます。 モデル デバッグ ビューを調べて、規約によって作成されたプロパティ名を確認できます。

modelBuilder
    .Entity<Post>()
    .HasData(new Post { PostId = 1, Title = "First" });

modelBuilder
    .Entity<Tag>()
    .HasData(new Tag { TagId = "ef" });

modelBuilder
    .Entity<Post>()
    .HasMany(p => p.Tags)
    .WithMany(p => p.Posts)
    .UsingEntity(j => j.HasData(new { PostsPostId = 1, TagsTagId = "ef" }));

結合エンティティ型には追加のデータを格納できますが、そのためには、特注の CLR 型を作成することをお勧めします。 カスタム結合エンティティ型を使用してリレーションシップを構成する場合は、両方の外部キーを明示的に指定する必要があります。

internal class MyContext : DbContext
{
    public MyContext(DbContextOptions<MyContext> options)
        : base(options)
    {
    }

    public DbSet<Post> Posts { get; set; }
    public DbSet<Tag> Tags { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>()
            .HasMany(p => p.Tags)
            .WithMany(p => p.Posts)
            .UsingEntity<PostTag>(
                j => j
                    .HasOne(pt => pt.Tag)
                    .WithMany(t => t.PostTags)
                    .HasForeignKey(pt => pt.TagId),
                j => j
                    .HasOne(pt => pt.Post)
                    .WithMany(p => p.PostTags)
                    .HasForeignKey(pt => pt.PostId),
                j =>
                {
                    j.Property(pt => pt.PublicationDate).HasDefaultValueSql("CURRENT_TIMESTAMP");
                    j.HasKey(t => new { t.PostId, t.TagId });
                });
    }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public ICollection<Tag> Tags { get; set; }
    public List<PostTag> PostTags { get; set; }
}

public class Tag
{
    public string TagId { get; set; }

    public ICollection<Post> Posts { get; set; }
    public List<PostTag> PostTags { get; set; }
}

public class PostTag
{
    public DateTime PublicationDate { get; set; }

    public int PostId { get; set; }
    public Post Post { get; set; }

    public string TagId { get; set; }
    public Tag Tag { get; set; }
}

結合リレーションシップの構成

EF では、多対多リレーションシップを表すために、結合エンティティ型に 2 つの一対多リレーションシップが使用されます。 これらのリレーションシップは、UsingEntity 引数で構成できます。

modelBuilder.Entity<Post>()
    .HasMany(p => p.Tags)
    .WithMany(p => p.Posts)
    .UsingEntity<Dictionary<string, object>>(
        "PostTag",
        j => j
            .HasOne<Tag>()
            .WithMany()
            .HasForeignKey("TagId")
            .HasConstraintName("FK_PostTag_Tags_TagId")
            .OnDelete(DeleteBehavior.Cascade),
        j => j
            .HasOne<Post>()
            .WithMany()
            .HasForeignKey("PostId")
            .HasConstraintName("FK_PostTag_Posts_PostId")
            .OnDelete(DeleteBehavior.ClientCascade));

注意

多対多リレーションシップを構成する機能は、EF Core 5.0 で導入されました。以前のバージョンには、次の方法を使用します。

間接的な多対多リレーションシップ

結合エンティティ型を追加し、2 つの個別の一対多リレーションシップをマッピングするだけで、多対多リレーションシップを表すこともできます。

public class MyContext : DbContext
{
    public MyContext(DbContextOptions<MyContext> options)
        : base(options)
    {
    }

    public DbSet<Post> Posts { get; set; }
    public DbSet<Tag> Tags { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<PostTag>()
            .HasKey(t => new { t.PostId, t.TagId });

        modelBuilder.Entity<PostTag>()
            .HasOne(pt => pt.Post)
            .WithMany(p => p.PostTags)
            .HasForeignKey(pt => pt.PostId);

        modelBuilder.Entity<PostTag>()
            .HasOne(pt => pt.Tag)
            .WithMany(t => t.PostTags)
            .HasForeignKey(pt => pt.TagId);
    }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public List<PostTag> PostTags { get; set; }
}

public class Tag
{
    public string TagId { get; set; }

    public List<PostTag> PostTags { get; set; }
}

public class PostTag
{
    public DateTime PublicationDate { get; set; }

    public int PostId { get; set; }
    public Post Post { get; set; }

    public string TagId { get; set; }
    public Tag Tag { get; set; }
}

注意

データベースから多対多リレーションシップのスキャフォールディングを行うためのサポートは、まだ追加されていません。 問題の追跡を参照してください。

その他のリソース