リレーションシップでの外部キーとプリンシパル キー

すべての一対一一対多のリレーションシップは、従属側の外部キーがプリンシパル側の主キーまたは代替キーを参照することで定義します。 便宜上、この主キーまたは代替キーをリレーションシップの "プリンシパル キー" と呼びます。 多対多のリレーションシップは 2 つの一対多のリレーションシップで構成され、それぞれプリンシパル キーを参照する外部キーによって定義されています。

ヒント

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

外部キー

多くの場合、外部キーを構成する 1 つ以上のプロパティが規則によって検出されます。 プロパティは、マッピング属性またはモデル構築 API の HasForeignKey を使って明示的に構成することもできます。 HasForeignKey はラムダ式に使用できます。 たとえば、1 つのプロパティで構成される外部キーの場合です。

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 でラムダ式を使うと、コード分析とリファクタリングにプロパティを使用できます。また、API にプロパティの型を渡し、さらにチェーンされたメソッドに使うこともできます。

HasForeignKey に外部キーのプロパティ名を文字列として渡すこともできます。 たとえば、1 つのプロパティの場合:

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

文字列を使うと、次のような場合に役立ちます。

  • 1 つ以上のプロパティがプライベートです。
  • 1 つ以上のプロパティがエンティティ型に存在せず、シャドウ プロパティとして作成する必要があります。
  • プロパティ名は、モデル構築プロセスへの何らかの入力に基づいて計算または構築されます。

null 非許容外部キー列

オプションと必須のリレーションシップに関する記事で説明されているように、リレーションシップがオプションか必須かは、外部キー プロパティの null 値の許容で決まります。 ただし、[Required] 属性を使用するか、モデル構築 API で IsRequired を呼び出すことで、Null 許容外部キー プロパティを必須のリレーションシップに使用できます。 次に例を示します。

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

または、外部キーが規則によって検出された場合、HasForeignKey を呼び出すことなく IsRequired を使用できます。

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 許容になります。

シャドウ外部キー プロパティは明示的に作成することもできます。これはプロパティのファセットを構成する場合に便利です。 たとえば、プロパティを 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 サポートなどのファセットをリレーションシップのプリンシパル キーから継承します。 そのため、外部キー プロパティにファセットを明示的に構成する必要があることはほとんどありません。

指定された名前がエンティティ型のどのプロパティとも一致しない場合、シャドウ プロパティの作成は 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 は外部キーの 1 つ以上のプロパティに対してデータベース インデックスを作成します。 規則によって作成されるインデックスの種類の詳細については、モデル構築の規則に関する記事を参照してください。

ヒント

EF モデルの場合、そのモデルに含まれるエンティティ型間にリレーションシップが定義されます。 たとえば、BoundedContext パターンを使う場合など、リレーションシップによっては、異なるコンテキストのモデル内のエンティティ型を参照する必要があります。 このような状況では、外部キー列を通常のプロパティにマップし、これらのプロパティを手動で操作してリレーションシップの変更を処理できるようにする必要があります。

プリンシパル キー

規則により、外部キーはリレーションシップのプリンシパル側で主キーに制約されます。 ただし、代わりに代替キーを使用できます。 これを実現するには、モデル構築 API で HasPrincipalKey を使います。 たとえば、1 つのプロパティ外部キーの場合:

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 に代替キーのプロパティ名を文字列として渡すこともできます。 たとえば、1 つのプロパティ キーの場合:

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 を使用できます。 詳細については、「キー」を参照してください。

キーなしエンティティへのリレーションシップ

すべてのリレーションシップには、プリンシパル (主または代替) キーを参照する外部キーが必要です。 つまり、参照する外部キーのプリンシパル キーはないため、キーなしエンティティ型はリレーションシップのプリンシパル側として機能できません。

ヒント

エンティティ型は、主キーを持たずに代替キーを持つことはできません。 この場合、代替キー (複数ある場合は代替キーの 1 つ) を主キーに昇格させる必要があります。

ただし、キーなしエンティティ型でも外部キーを定義できるので、リレーションシップの依存側として機能できます。 たとえば、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"));
}