一対多のリレーションシップ

1 つのエンティティが任意の数の他のエンティティに関連付けられている場合は、一対多リレーションシップを使います。 たとえば、1 つの Blog には多数の Posts を関連付けることができますが、各 Post は 1 つの Blog だけと関連付けられます。

このドキュメントは、多くの例を中心に構成されています。 最初に一般的なケースの例をいくつか示し、そこで概念も紹介します。 後の例では、あまり一般的でない種類の構成について説明します。 ここでの適切なアプローチは、最初にいくつかの例と概念を理解してから、具体的なニーズに基づいて後の例に進むことです。 このアプローチに基づいて、単純な "必須" と "オプション" の一対多リレーションシップから始めます。

ヒント

以下のすべての例のコードは、OneToMany.cs にあります。

必須の一対多

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

一対多リレーションシップは、次のもので構成されます。

  • プリンシパル エンティティの 1 つ以上の主キーまたは代替キー プロパティ。これは、リレーションシップの "一" の端です。 たとえば、Blog.Id のようにします。
  • 依存エンティティの 1 つ以上の外部キー プロパティ。これは、リレーションシップの "多" の端です。 たとえば、Post.BlogId のようにします。
  • 必要に応じて、依存エンティティを参照するプリンシパル エンティティでのコレクション ナビゲーション。 たとえば、Blog.Posts のようにします。
  • 必要に応じて、プリンシパル エンティティを参照する依存エンティティに対する参照ナビゲーション。 たとえば、Post.Blog のようにします。

したがって、この例のリレーションシップの場合は、次のようになります。

  • 外部キー プロパティ Post.BlogId は null 許容ではありません。 これにより、すべての依存側 (Post) は "何らかのプリンシパル (Blog) に関連付けられている必要があり"、外部キー プロパティには何らかの値が設定されている必要があるため、リレーションシップは "必須" になります。
  • 両方のエンティティには、リレーションシップの反対側にある 1 つまたは複数の関連エンティティを指すナビゲーションがあります。

注意

必須リレーションシップにより、すべての依存エンティティを何らかのプリンシパル エンティティに関連付ける必要があることが保証されます。 ただし、プリンシパル エンティティは依存エンティティなしで "常に" 存在できます。 つまり、必須リレーションシップは、少なくとも 1 つの依存エンティティが常に存在することを示すわけでは "ありません"。 プリンシパルが特定の数の依存に確実に関連付けられるようにする方法は EF モデルにはなく、リレーショナル データベースにもそれを行う標準的な方法はありません。 これが必要な場合は、アプリケーション (ビジネス) ロジックで実装する必要があります。 詳細については、「必須のナビゲーション」を参照してください。

ヒント

2 つのナビゲーション (1 つは依存側からプリンシパル側、もう 1 つは逆のプリンシパル側から依存側) を持つリレーションシップは、双方向リレーションシップと呼ばれます。

このリレーションシップは、規則によって検出されます。 つまり、

  • Blog はリレーションシップのプリンシパル側として検出され、Post は依存側として検出されます。
  • Post.BlogId は、プリンシパル側の Blog.Id 主キーを参照する依存側の外部キーとして検出されます。 Post.BlogId が null 許容ではないので、リレーションシップは必須として検出されます。
  • Blog.Posts は、コレクション ナビゲーションとして検出されます。
  • Post.Blog は、参照ナビゲーションとして検出されます。

重要

C# の null 許容参照型を使用するとき、外部キー プロパティが null 許容の場合は、参照ナビゲーションは null 許容である必要があります。 外部キー プロパティが null 非許容の場合は、参照ナビゲーションは null 許容でも非許容でもかまいません。 この例の場合、Post.BlogId は null 非許容であり、Post.Blog も null 非許容です。 EF は通常 Blog インスタンスを設定し、完全に読み込まれたリレーションシップではそれを null にできないため、= null!; コンストラクトを使ってこれは意図的なものであると C# コンパイラに伝えます。 詳細については、「Null 許容参照型の使用」を参照してください。

リレーションシップのナビゲーション、外部キー、または必須とオプションの性質が規則によって検出されない場合は、これらの設定を明示的に構成できます。 次に例を示します。

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

上の例では、リレーションシップの構成はプリンシパル エンティティ型 (Blog) の HasMany で始まり、その後に WithOne が続きます。 すべてのリレーションシップと同様に、これは依存エンティティ型 (Post) で始まり、HasOne を使用して、WithMany がそれに続くのとまったく同じです。 次に例を示します。

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

これらのオプションは、どちらかが他より優れているということはありません。両方ともまったく同じ構成になります。

ヒント

プリンシパル側から一度開始し、依存側からもう一度開始するというように、リレーションシップを 2 回構成する必要はありません。 また、リレーションシップのプリンシパル側と依存側を半分ずつ個別に構成しようとしても、通常はうまくいきません。 どちらか一方の側から各リレーションシップを構成することを選んだら、構成コードを 1 回だけ記述します。

オプションの一対多

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int? BlogId { get; set; } // Optional foreign key property
    public Blog? Blog { get; set; } // Optional reference navigation to principal
}

これは前の例と同じですが、外部キー プロパティとプリンシパルへのナビゲーションが null 許容である点が異なります。 これにより、依存側 (Post) はどのプリンシパル側 (Blog) に関連付けられて "いなくても" 存在できるため、リレーションシップは "オプション" になります。

重要

C# の null 許容参照型を使用するとき、外部キー プロパティが null 許容の場合は、参照ナビゲーションは null 許容である必要があります。 この場合、Post.BlogId は null 許容であるため、Post.Blog も null 許容である必要があります。 詳細については、「Null 許容参照型の使用」を参照してください。

前と同じように、このリレーションシップは規則によって検出されます。 リレーションシップのナビゲーション、外部キー、または必須とオプションの性質が規則によって検出されない場合は、これらの設定を明示的に構成できます。 次に例を示します。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey(e => e.BlogId)
        .IsRequired(false);
}

シャドウ外部キーを持つ必須の一対多

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

外部キーはデータベースでのリレーションシップの表現方法の詳細であり、純粋にオブジェクト指向の方法でリレーションシップを使うときは必要ないため、モデルに外部キー プロパティが必要ない場合があります。 ただし、エンティティがシリアル化される場合 (たとえば、ネットワーク経由で送信するため)、外部キー値は、エンティティがオブジェクト形式ではないときにリレーションシップ情報を正確に保持するための便利な方法になることがあります。 そのため、多くの場合、この目的のために外部キー プロパティを .NET 型で保持することは実用的です。 外部キーのプロパティはプライベートにすることができます。これは多くの場合、エンティティと一緒にその値を転送できるようにしながら、外部キーが公開されるのを防ぐための、適切な妥協案です。

前の 2 つの例に続いて、この例では、依存エンティティ型から外部キー プロパティを削除します。 そのため、EF は int 型で BlogId という名前のシャドウ外部キー プロパティを作成します。

ここで注意すべき重要な点は、C# の null 許容参照型が使われているため、外部キー プロパティが null 許容かどうか、したがってリレーションシップがオプションか必須かの判断には、参照ナビゲーションの null 許容性が使われるということです。 null 許容参照型が使われていない場合は、シャドウ外部キー プロパティは既定で null 許容になり、リレーションシップは既定でオプションになります。 この場合は、IsRequired を使ってシャドウ外部キー プロパティを強制的に null 非許容にして、リレーションシップを必須にします。

前と同じように、このリレーションシップは規則によって検出されます。 リレーションシップのナビゲーション、外部キー、または必須とオプションの性質が規則によって検出されない場合は、これらの設定を明示的に構成できます。 次に例を示します。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey("BlogId")
        .IsRequired();
}

シャドウ外部キーを持つオプションの一対多

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public Blog? Blog { get; set; } // Optional reference navigation to principal
}

前の例と同様に、依存エンティティ型からは外部キー プロパティが削除されています。 そのため、EF は int? 型で BlogId という名前のシャドウ外部キー プロパティを作成します。 前の例とは異なり、今度は、C# の null 許容参照型が使われていて、依存エンティティ型でのナビゲーションが null 許容であるため、外部キー プロパティは null 許容として作成されます。 これにより、リレーションシップはオプションになります。

C# の null 許容参照型が使われていない場合は、外部キー プロパティも既定で null 許容として作成されます。 つまり、自動的に作成されるシャドウ プロパティを持つリレーションシップは、既定でオプションになります。

前と同じように、このリレーションシップは規則によって検出されます。 リレーションシップのナビゲーション、外部キー、または必須とオプションの性質が規則によって検出されない場合は、これらの設定を明示的に構成できます。 次に例を示します。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey("BlogId")
        .IsRequired(false);
}

プリンシパル側へのナビゲーションのない一対多

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
}

この例では、外部キー プロパティが再び導入されていますが、依存側でのナビゲーションは削除されています。

ヒント

ナビゲーションが 1 つだけのリレーションシップ (依存側からプリンシパル側、またはプリンシパル側から依存側のどちらか一方だけ) は、一方向リレーションシップと呼ばれます。

前と同じように、このリレーションシップは規則によって検出されます。 リレーションシップのナビゲーション、外部キー、または必須とオプションの性質が規則によって検出されない場合は、これらの設定を明示的に構成できます。 次に例を示します。

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

WithOne の呼び出しに引数がないことに注意してください。 これは、Post から Blog へのナビゲーションがないことを EF に伝える方法です。

ナビゲーションのないエンティティから構成が始まる場合は、リレーションシップの他の端にあるエンティティの型を、ジェネリック HasOne<>() の呼び出しを使って明示的に指定する必要があります。 次に例を示します。

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

プリンシパル側へのナビゲーションがなく、シャドウ外部キーを持つ一対多

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
}

この例では、外部キー プロパティと依存側でのナビゲーションの両方を削除することにより、前の 2 つの例を組み合わせます。

このリレーションシップは、オプションのリレーションシップとして規則により検出されます。 それを必須にする必要があることを示すために使用できる手段はコードにはないため、必須のリレーションシップを作成するには、IsRequired を使った最小限の構成が必要です。 次に例を示します。

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

必要に応じて IsRequired() または IsRequired(false) の適切な呼び出しを行い、さらに完全な構成を使って、ナビゲーションと外部キー名を明示的に構成できます。 次に例を示します。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne()
        .HasForeignKey("BlogId")
        .IsRequired();
}

依存側へのナビゲーションのない一対多

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

前の 2 つの例では、プリンシパル側から依存側へのナビゲーションはありましたが、依存側からプリンシパル側へのナビゲーションはありませんでした。 次の 2 つの例では、依存側にナビゲーションを再び導入しますが、代わりにプリンシパル側のナビゲーションを削除します。

前と同じように、このリレーションシップは規則によって検出されます。 リレーションシップのナビゲーション、外部キー、または必須とオプションの性質が規則によって検出されない場合は、これらの設定を明示的に構成できます。 次に例を示します。

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

ここでも、引数なしで WithMany() を呼び出し、この方向のナビゲーションがないことを示していることに注意してください。

ナビゲーションのないエンティティから構成が始まる場合は、リレーションシップの他の端にあるエンティティの型を、ジェネリック HasMany<>() の呼び出しを使って明示的に指定する必要があります。 次に例を示します。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany<Post>()
        .WithOne(e => e.Blog)
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

ナビゲーションのない一対多

場合によっては、ナビゲーションのないリレーションシップを構成すると便利なことがあります。 このようなリレーションシップは、外部キーの値を直接変更することによってのみ操作できます。

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
}

2 つの型が関連していることを示すナビゲーションがないため、このリレーションシップは規則によって検出されません。 OnModelCreating で明示的に構成できます。 次に例を示します。

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

この構成では、Post.BlogId プロパティは規則によって引き続き外部キーとして検出され、外部キー プロパティが null 許容ではないため、リレーションシップは必須です。 外部キー プロパティを null 許容にすることで、リレーションシップを "オプション" にできます。

このリレーションシップのより完全な明示的な構成は次のとおりです。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany<Post>()
        .WithOne()
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

代替キーを使用した一対多

ここまでのすべての例では、依存側の外部キー プロパティは、プリンシパル側の主キー プロパティに制約されています。 代わりに、外部キーを別のプロパティに制約でき、これによりそれはプリンシパル エンティティ型の代替キーになります。 次に例を示します。

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public int AlternateId { get; set; } // Alternate key as target of the Post.BlogId foreign key
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

EF は常に規則によって主キーへのリレーションシップを作成するため、このリレーションシップは規則によって検出されません。 OnModelCreatingHasPrincipalKey の呼び出しを使って明示的に構成できます。 次に例を示します。

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

HasPrincipalKey を他の呼び出しと組み合わせて、ナビゲーション、外部キー プロパティ、必須とオプションの性質を明示的に構成できます。 次に例を示します。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasPrincipalKey(e => e.AlternateId)
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

複合外部キーを使用した一対多

これまでのすべての例では、プリンシパル側の主キー プロパティまたは代替キー プロパティは 1 つのプロパティで構成されていました。 主キーまたは代替キーは、複数のプロパティで構成することもできます。これらは "複合キー" と呼ばれます。 リレーションシップのプリンシパル側に複合キーがある場合、依存側の外部キーも同じ数のプロパティを持つ複合キーである必要があります。 次に例を示します。

// Principal (parent)
public class Blog
{
    public int Id1 { get; set; } // Composite key part 1
    public int Id2 { get; set; } // Composite key part 2
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId1 { get; set; } // Required foreign key property part 1
    public int BlogId2 { get; set; } // Required foreign key property part 2
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

このリレーションシップは、規則によって検出されます。 ただし、複合キー自体を明示的に構成する必要があります。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasKey(e => new { e.Id1, e.Id2 });
}

重要

複合外部キーの値は、そのプロパティの値のいずれかが null の場合、null と見なされます。 1 つのプロパティが null でそれ以外は null ではない複合外部キーは、同じ値を持つ主キーまたは代替キーと一致するものと見なされません。 どちらも null と見なされます。

HasForeignKeyHasPrincipalKey の両方を使って、複数のプロパティを持つキーを明示的に指定できます。 次に例を示します。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>(
        nestedBuilder =>
        {
            nestedBuilder.HasKey(e => new { e.Id1, e.Id2 });

            nestedBuilder.HasMany(e => e.Posts)
                .WithOne(e => e.Blog)
                .HasPrincipalKey(e => new { e.Id1, e.Id2 })
                .HasForeignKey(e => new { e.BlogId1, e.BlogId2 })
                .IsRequired();
        });
}

ヒント

上のコードでは、HasKeyHasMany の呼び出しが、入れ子になったビルダーにグループ化されています。 入れ子になったビルダーを使うと、同じエンティティ型に対して Entity<>() を複数回呼び出す必要がなくなりますが、機能的には Entity<>() を複数回呼び出すのと同じです。

連鎖削除のない必須の一対多

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

規則により、必須のリレーションシップは連鎖削除を行うように構成されます。つまり、依存側はプリンシパル側なしではデータベースに存在できないため、プリンシパル側が削除されると、その依存側もすべて削除されます。 存在しなくなった依存側の行を自動的に削除するのではなく、例外をスローするように EF を構成できます。

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

自己参照の一対多

これまでのすべての例では、プリンシパル エンティティ型と依存エンティティ型は異なっていました。 そうなっていなければならないわけではありません。 たとえば、次の型では、各 Employee が他の Employees に関連付けられています。

public class Employee
{
    public int Id { get; set; }

    public int? ManagerId { get; set; } // Optional foreign key property
    public Employee? Manager { get; set; } // Optional reference navigation to principal
    public ICollection<Employee> Reports { get; } = new List<Employee>(); // Collection navigation containing dependents
}

このリレーションシップは、規則によって検出されます。 リレーションシップのナビゲーション、外部キー、または必須とオプションの性質が規則によって検出されない場合は、これらの設定を明示的に構成できます。 次に例を示します。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Employee>()
        .HasOne(e => e.Manager)
        .WithMany(e => e.Reports)
        .HasForeignKey(e => e.ManagerId)
        .IsRequired(false);
}