model-building について
model-building は、Entity Framework Core の規約、メタデータ、初期化、リレーショナルモデルに関する開発リファレンスです。変更の実装範囲や互換性への配慮、対象を絞ったテスト方法を扱います。
主な機能
- 01規約システムとメタデータ階層
- 02設定可能なメタデータ属性の実装
- 03モデルのライフサイクルと初期化
- 04ランタイムモデルとコンパイル済みモデルへの伝播
- 05スナップショット互換性の指針
- 06モデル検証とテストの場所
活用シーン
- 01設定可能なメタデータ属性の追加
- 02規約やリレーションシップ検出のデバッグ
- 03アノテーションのランタイムモデルへの伝播
- 04モデル変更後の予期しないマイグレーションの防止
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dotnet/ efcore --skill model-buildingインストール後、Claude Code で/model-buildingを入力して読み込まれたか確認します。
詳細
モデルの構築、規約、初期化
モデルの構築(規約、流暢な API、メタデータ階層)とモデルの初期化(実行時のアノテーションの伝播、コンパイル済みモデルのフィルタリング)について説明します。
規約システム
ConventionSet(src/EFCore/Metadata/Conventions/ConventionSet.cs)には、メタデータの各イベントに対応する List<I*Convention> が格納されています。主要な規約は src/EFCore/Metadata/Conventions/ にあります。
DbSetFindingConvention—DbSet<T>からエンティティを検出しますPropertyDiscoveryConvention— CLR 型からプロパティを検出しますKeyDiscoveryConvention— 主キー(Id、TypeId)を検出しますRelationshipDiscoveryConvention— ナビゲーションから外部キーを推測しますRuntimeModelConvention— 変更可能なモデルから最適化されたRuntimeModelを作成します
規約を追加または削除するには、ConfigureConventions(ModelConfigurationBuilder) をオーバーライドします。
規約の修正では、ディスパッチャーの動作と曖昧さに関する情報を保持する必要があります。逆ナビゲーションの属性については、曖昧なナビゲーションを恒久的に無視して再帰を解決しないでください。候補を一つ削除すると、別の曖昧さが一意に見えるようになり、誤ったリレーションシップが構成される可能性があります。InversePropertyAttributeConvention は逆ナビゲーションの候補を記録し、規約の処理を続行できるようにしながら、AddInverseNavigation、TryRemoveIfAmbiguous、FindAmbiguousInverse を使用します。
最初に曖昧なモデルだけでなく、状態遷移もテストしてください。まず、複数の候補が相互に影響し合う場合にリレーションシップが構成されないことを確認します。次に、競合するナビゲーションを無視または削除する方法と、競合するエンティティ型を削除する方法の両方で実際に曖昧さを取り除き、残った逆リレーションシップが構成されることを確認します。これらのケースでは、候補の追跡と恒久的に無視する回避策とを区別できます。
メタデータのインターフェイス階層
IReadOnly* → IMutable* → IConvention* → IRuntime*
対象:Model、EntityType、Property、Key、ForeignKey、Navigation、Index など。ビルダーの階層:*Builder → IConvention*Builder。
構成可能なメタデータファセットの追加
公開インターフェイスだけで終わらせず、最も近い既存のファセットを端から端までたどってください。
- 読み取り専用、変更可能、規約用のメタデータコントラクト、具象ストレージ、既定値、構成ソース、規約セッター、ビルダーの優先順位チェックを追加します。メタデータの置き換え時やリレーションシップの再構築時にも、ファセットを保持してください。
- 変更が規約から観察できる場合は、規約インターフェイス、
ConventionSetの追加/置換/削除のすべての経路、即時および遅延ディスパッチャーのスコープとノードを追加します。 RuntimeModelConvention、実行時のコンストラクター/ビルダー、CSharpRuntimeModelCodeGeneratorを通じて、最適化された実行時メタデータに値を引き継ぎ、コンパイル済みモデルでも保持されるようにします。- スナップショットにファセットを保持する場合は、流暢なビルダー API と
CSharpSnapshotGeneratorを更新します。従来の既定値に相当するものとして、以前からの不在状態を維持してください。 - 公開 API のベースラインを更新し、構成ソースの優先順位、規約のディスパッチ、実行時/コンパイル済みモデルへの伝播、スナップショットの往復を網羅します。
モデルのライフサイクル
- 変更可能なモデル —
OnModelCreating中にModelBuilderによって構築され、FinalizeModel()によって読み取り専用になります - 設計時モデル — マイグレーションで使用する設計時専用のアノテーションも含む、確定済みの読み取り専用
Model - 実行時モデル —
RuntimeModelConvention.ProcessModelFinalized()によって作成される最適化された読み取り専用モデルで、設計時専用のアノテーションは含みません
ModelRuntimeInitializer.Initialize()(DbContextServices.CreateModel() から呼び出されます):
Initialize(model, designTime, validationLogger)
├─ FinalizeModel() if mutable
├─ Set ModelDependencies, InitializeModel
└─ RuntimeModelConvention creates RuntimeModel, copies/filters annotations
複合型プロパティの再帰処理
規約または検証でプロパティを処理する際は、複合型に独自の宣言済みプロパティが含まれる場合があることに注意してください。すべてのプロパティ(入れ子になったコレクションでない複合型のプロパティを含む)を反復処理するには GetFlattenedProperties() を使用するか、GetDeclaredComplexProperties() → complexProperty.ComplexType を手動で再帰処理します。
新しいアノテーションの追加
CoreAnnotationNamesとそのAllNamesに定数を追加します- 設計時のみのアノテーション(マイグレーション操作でのみ使用される)の場合は、
RuntimeModelConvention.ProcessModelAnnotationsでフィルターします- フィルターで除外された場合は、ランタイムモデル上でアクセスされたときに例外をスローするロジックをゲッターに追加します
- 実行時に遅延計算できる場合(例:他のアノテーションに基づく場合)は、
CSharpRuntimeAnnotationCodeGenerator.Generateでフィルターします - アップマイグレーションまたはリレーショナルモデルで使用する場合は
RelationalAnnotationProviderに、ダウンマイグレーションで使用する場合はIMigrationsAnnotationProviderに伝播させます
リレーショナルモデル
RelationalModel(src/EFCore.Relational/Metadata/Internal/RelationalModel.cs)は、エンティティ型を物理データベースオブジェクト(Tables、Views、Functions、Queries、DefaultTables)にマッピングする、EF モデルのデータベース中心のビューです。DefaultTables は、FromSql クエリでのみ使用される疑似テーブルオブジェクトです。
RelationalModelRuntimeInitializer によって遅延作成され、model.GetRelationalModel() 経由でアクセスされます。マイグレーション(MigrationsModelDiffer)、更新パイプライン、クエリパイプラインで使用されます。
RelationalAnnotationProvider は、リレーショナルモデルの要素にアノテーションを設定します。プロバイダーのサブクラス(例:SqlServerAnnotationProvider)は、プロバイダー固有のアノテーションを追加します。IMigrationsAnnotationProvider は、ダウンマイグレーション操作で使用されるアノテーションを制御します。
モデルの検証
ModelValidator(src/EFCore/Infrastructure/ModelValidator.cs)と RelationalModelValidator(src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs)は、モデルの確定後、ModelRuntimeInitializer.Initialize() 中に、検証前と検証後の InitializeModel 呼び出しの間で実行されます。
マイグレーションスナップショットとの互換性
モデル構築の変更によって、アップグレードしたユーザーに不要なマイグレーションが発生することがあります。原因は次の 2 つです。
- 新しいメタデータがスナップショットに書き込まれる — 古いスナップショットには含まれていないため、
MigrationsModelDifferが差分を検出します。対処法:古いスナップショットにアノテーションが存在しない場合は、古い既定値として扱われるようにします。 - アノテーションの名前が変更される、または解釈が変わる — 古いスナップショットから異なるモデルが生成されます。対処法:後方互換性のある読み取りロジックを維持します。
CSharpSnapshotGenerator(書き込まれる内容)と MigrationsModelDiffer(存在しない場合の扱い)を確認します。test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs にスナップショットのラウンドトリップテストを追加します。
テスト
| 項目 | 場所 |
|---|---|
| 規約の単体テスト | test/EFCore.Tests/Metadata/Conventions/ |
| メタデータの単体テスト | test/EFCore.Tests/Metadata/Internal/ |
| モデルビルダー API のテスト | test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest*.cs |
| リレーションシップ検出のテスト | test/EFCore.Specification.Tests/ModelBuilding101*.cs |
| モデル検証のテスト | test/EFCore.Tests/Infrastructure/ModelValidatorTest*.cs |
| コンパイル済みモデルのテスト | test/EFCore.Specification.Tests/Scaffolding/CompiledModelTestBase.cs |
検証
- モデルの確定中に
InvalidOperationExceptionが発生せず、モデルを構築できる - 新しい API がすべてテストでカバーされている
EF_TEST_REWRITE_BASELINES=1を指定しても、コンパイル済みモデルのベースラインが正常に更新される- メタデータオブジェクトの
ToString()が、例外をスローせず簡潔な内容を表示する - 既存のスナップショットを持つプロジェクトに対して不要なマイグレーションが生成されない