【C#】Entity Framework Core -リレーション成立条件-

こんにちは、えいたです。

今回は、.NET の ORM である Entity Framework Core(EF Core) におけるリレーションが、どのように成立するのかについて整理してみます。

これまで私は Laravel(Eloquent)を中心に開発してきたため、リレーションは「モデルで明示的に定義して使うもの」という感覚がありました。
Laravel では hasManybelongsTo を定義してはじめて、Eloquent 上でリレーションとして扱えます。

しかし EF Core に触れたときに感じたのは、「書いていないのに関係が成立している」 という違和感でした。
EF Core では、ナビゲーションプロパティや命名規約から、エンティティ同士の関係が自動的に解釈されます。
つまり、明示しなくてもリレーションが構築される場面があります。

本記事では、EF Core におけるリレーションの成立条件に焦点を当て、

- 何をもって関係が決まるのか
- 明示しないとどうなるのか

を見ていきます。

EF Core でのリレーションは基本的に3パターンで成立する

EF Core のリレーションは、エンティティクラスの構造から自動的に解釈されます。
代表的には、次の3パターンで関係が検出されます。

1. ナビゲーションのみ
2. 外部キーのみ
3. ナビゲーション+外部キー

※ ただし、命名や構造が曖昧な場合は、関係が検出されない/例外になることもあります。

ナビゲーションのみ

public class Post
{
    public int Id { get; set; }
    public User User { get; set; } // ナビゲーション
}

User という参照型プロパティがあるだけでも、EF Core は「関連エンティティへのナビゲーション」と解釈します。

このとき、対応する外部キーがクラス内に存在しない場合でも、EF Core は内部的に シャドウプロパティ を生成して関係を保持します。

外部キーのみ

public class Post
{
    public int Id { get; set; }
    public int UserId { get; set; } // 外部キー
}

UserId のように規約に合致するプロパティは、外部キー候補として認識されます。
ナビゲーションがなくても、リレーションとして扱われます。

ナビゲーション+外部キー

public class Post
{
    public int Id { get; set; }
    public int UserId { get; set; } // 外部キー
    public User User { get; set; }  // ナビゲーション
}

外部キーと参照ナビゲーションが両方あるため、EF Core が規約ベースで関係を解釈しやすい形です。
関係自体は片方だけでも成立しますが、両方あることで意図が明確になり、推論の曖昧さを減らせます。

では、なぜこれだけで成立するのか。次にその裏側を見ていきます。

なぜそれで成立するのか(推論の仕組み)

端的に言うと、エンティティクラス(テーブル構造を定義しているクラス)をもとに、リレーション情報が内部モデル(IModel)に自動的に登録されます。

この自動登録に使われるのが Convention(規約) です。

EF Core は エンティティクラスの次の情報を組み合わせて関係を決定します。

- プロパティ名(命名規則)
- プロパティの型(PK と互換性のある型)
- 構造(ナビゲーション / コレクション有無)

この3つをもとに、内部的にリレーションが登録されます。

つまり EF Core は、「書いていない」のではなく、「規約に従って解釈している」

これがリレーション成立の正体です。

以下は、EF Core が規約ベースで「何を見て」「どう解釈するか」をざっくり整理した表です。
厳密には例外や個別設定もありますが、リレーション推論の全体像をつかむにはかなり有効かなと思います。

EF Core のリレーション図

規約と明示設定 ― OnModelCreating の役割

先ほどお話しした通り、EF Core はまず Convention(規約)にもとづいて、エンティティの構造からリレーションを自動的に「検出・決定」します。

ただ、規約での推論は記述量を減らせる一方で、

- 命名が少しズレると検出されない
- 1対1などは従属側が決められず例外になることがある
- 複数ナビゲーションがあると意図と違う解釈になることがある

といった「挙動が読みにくい」場面が出ます。

こうした場面で、リレーションの解釈を明示的に固定したいときに使うのが OnModelCreating です。
HasMany / WithOne などで、規約の解釈を 明示的に固定・上書き できます。

※補足すると、OnModelCreating はリレーション設定専用の場所ではありません。
テーブル名・カラム名・主キー・複合キー・インデックス・必須/任意・文字数・変換(ValueConverter)など、エンティティのマッピング全体を定義する場所です。
今回はテーマがリレーションなので、HasMany / WithOne / HasForeignKey などの関係設定に絞って説明しています。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<User>()
        .HasMany(u => u.Posts)
        .WithOne(p => p.User)
        .HasForeignKey(p => p.UserId);
}

規約通りなら書かなくても成立しますが、次のときは明示する価値があります。

- 命名が規約に従っていない
- 1対1を確実に定義したい
- 外部キー名を固定したい
- Cascade 動作を制御したい
- 曖昧な関係を排除したい(意図を固定したい)
- チームでの可読性を上げたい

ここで一度、規約と明示設定の関係を整理すると、EF Core におけるリレーションは次の順序で決定されます。

まずは、初回のモデル構築時にリレーションがどう決まるのか を流れで見てみます。

① 初回のモデル構築時に走る処理(リレーション決定フロー)

エンティティクラス構造を読み込む
(ナビ / FK / 命名)
↓
Convention(規約)で関係を推論
↓
内部モデル(IModel)に登録
↓
OnModelCreating(Fluent API での明示設定)
↓
最終的なリレーション確定
(外部キーを軸とした関係)

また個人的に、リレーションを含むモデル定義(IModel)がいつ確定するのか も気になったので、補足として置いておきます。
EF Core では、リレーションは毎回推論されるわけではなく、初回のモデル構築時に確定した内容が IModel としてキャッシュされ、以降は再利用されます。

② IModel のキャッシュと再利用(DbContext 型ごと)

初回(DbContext 型ごと)
↓
IModel を構築・確定
(上のフロー一式がここで実行される)
↓
IModel をキャッシュ保存
↓
2回目以降(同じ DbContext 型)
↓
キャッシュ済み IModel を取得
↓
モデル再利用
(関係推論 / OnModelCreating は基本再実行されない)

まとめ

ここまで、EF Core におけるリレーション成立の流れ(規約による検出 → 必要に応じた明示設定)を整理しながら見てきました。
Laravel との違いという観点で一言でまとめると、EF Core は 「モデルに関係を書いたから成立する」のではなく、クラス構造を規約で解釈して関係を組み立てる ORM」 だと言えます。

一方で、前提として EF Core / Laravel に関係なく、DBレベルで関連を成立させるうえで重要なのは外部キー(FK)です。
こうした ORMごとの規約や、FKまわりの自動補完ルール に着目すると、今後ほかのORMや似たような挙動に出会ったときも、違和感の正体を整理しながら理解しやすくなるのかなと感じました。

以上です!!

この記事がお役に立てば【 いいね 】のご協力をお願いいたします!
2
読み込み中...
2 票, 平均: 1.00 / 12
27
X facebook はてなブックマーク pocket

この記事をかいた人

About the author

えいた

WEB開発エンジニア
とりあえず外に出たい