デザイン時ツールのアーキテクチャ

デザイン時ツールは、モデルのスキャフォールディングや移行の管理などのデザイン時操作をブートストラップする EF の一部です。 デザイン時に使用するために DbContext オブジェクトをインスタンス化する役割を担います。

高度なツール フローの Mermaid ダイアグラム。

dotnet-ef と NuGet パッケージ マネージャー コンソール (PMC) EF Core ツールの 2 つの主要なエントリ ポイントがあります。 これらはどちらも、ユーザーのプロジェクトに関する情報を収集し、それらをコンパイルし、最終的に EFCore.Design.dll内のデザイン時エントリ ポイントを呼び出す ef.exe を呼び出す役割を担います。

dotnet-ef

dotnet-ef は、.NET ツール です。 dotnet- プレフィックスを使用すると、メインの dotnet コマンドの一部として呼び出すことができます: dotnet ef.

このコマンドには、スタートアップ プロジェクトとターゲット プロジェクトの 2 つの主要な入力があります。 dotnet-ef は、これらのプロジェクトに関する情報を読み取ってからコンパイルする役割を担います。

dotnet-ef フローの dotnet-ef flow の Mermaid ダイアグラム。

MSBuild .targets ファイルを挿入し、カスタム MSBuild ターゲットを呼び出すことによって、プロジェクトに関する情報を読み取ります。 .targets ファイルは、埋め込みリソースとして dotnet-ef にコンパイルされます。 ソースは、src/dotnet-ef/Resources/EntityFrameworkCore.targetsにあります。

マルチターゲット プロジェクトを処理するためのロジックが最初に少しあります。 基本的には、最初のターゲット フレームワークを選択し、それ自体を再呼び出します。 1 つのターゲット フレームワークが決定されると、AssemblyName、OutputPath、RootNamespace などのいくつかの MSBuild プロパティが取得されます。

プロジェクト情報を収集したら、スタートアップ プロジェクトをコンパイルします。 ターゲット プロジェクトも推移的にコンパイルされることを前提としています。

次に、dotnet-ef が ef.exeを呼び出します。

PMC ツール

PMC ツールは dotnet-ef と同様の機能を実行しますが、MSBuild ではなく Visual Studio API を使用します。 Microsoft.EntityFrameworkCore.Tools パッケージとして出荷されます。 これらは、init.ps1を介して NuGet パッケージ マネージャー コンソールに自動的に読み込まれる特別な VS 対応 PowerShell モジュール です。 dotnet-ef と同様に、各コマンドは、スタートアップ プロジェクトとターゲット プロジェクトという 2 つの主要な入力を受け取ります。 ただし、これらの既定値は IDE から取得されます。 ターゲット プロジェクトは、パッケージ マネージャー コンソール内で デフォルト プロジェクト として指定されたプロジェクトです。 スタートアップ プロジェクトは、既定で、ソリューション エクスプローラーでスタートアップ プロジェクトとして指定されたもの (スタートアップ プロジェクトとして設定) に設定されます。

PMCツールのフローにおけるマーメイドダイアグラム。

PMC ツールは、可能な限り EnvDTE API を介してプロジェクトに関する情報を収集します。 場合によっては、Common Project System (CPS) または MSBuild API にドロップダウンする必要があります。 最新の C# プロジェクト システム実装ソースは、GitHub の dotnet/project-system プロジェクトで使用できます。

情報を収集した後、ソリューション全体をビルドします。

ヒント

#9716 問題は、スタートアップ プロジェクトのみをビルドするように更新することです。

次に、dotnet-ef と同様に、ef.exeを呼び出します。 PMC ツールには、ef.exe を呼び出して、より統合されたエクスペリエンスを提供するためにコマンドによって作成されたすべてのファイルを開いた後に、少し追加のロジックがあります。

ef.exe

内部の人間と呼ばれることもあります。ef.exe (より良い名前がないため) は、dotnet-ef と PMC Tools の両方の一部としてバイナリのセットとして出荷されます。 ターゲット フレームワークとプラットフォームごとに、さまざまなバイナリがあります。

  • ツール/
    • net461/
      • any/
        • ef.exe
      • win-x86/
        • ef.exe
      • win-arm64/
        • ef.exe
    • netcoreapp2.0/
      • any/
        • ef.dll

.NET Framework アセンブリは、EF Core 3.1 プロジェクトおよび .NET Framework をターゲットとする以前のプロジェクトに対してのみ呼び出されます。 設計上、古いバージョンの EF を使用するプロジェクトでは、最新バージョンのツールを使用できます。 任意のディレクトリの下のアセンブリは、Windows の x64 と arm64 の両方のバージョンで x64 として実行される AnyCPU プラットフォームを対象としているため、x64 はありません。

.NET Core 2.0 アセンブリは、.NET Core または .NET 5 以降を対象とするプロジェクトに使用されます。

ef.exe の主な役割は、スタートアップ プロジェクトの出力アセンブリを読み込み、EFCore.Design.dll内のデザイン時エントリ ポイントを呼び出す必要があります。

.NET Framework では、別の AppDomain を使用して、プロジェクトの App/Web.config ファイルを渡してプロジェクト アセンブリを読み込み、NuGet またはユーザーによって追加されたリダイレクトを受け入れ、バインドします。

.NET Core/5 以降では、プロジェクトの .deps.json ファイルと .runtimeconfig.json ファイルを使用して ef.dll を呼び出し、プロジェクトの実際のランタイムとアセンブリの読み込み動作をエミュレートします。

dotnet exec ef.dll --depsfile startupProject.deps.json --runtimeconfig startupProject.runtimeconfig.json

ヒント

#18840 の問題は、主にユーザーのアセンブリの読み込みに するのではなく、dotnet exec を使用することです。 これにより、Android や iOS を対象とするプロジェクトを含む、より多くの種類のプロジェクトをツールで操作できるようになります。

すべてが読み込みの準備が整った後、リフレクションおよび Activator.CreateInstance(または .NET Framework 上の AppDomain.CreateInstance)を利用して、ef.exe が EFCore.Design.dll に呼び出しを行います。

EFCore.Design.dll

EFCore.Design.dll、またはより正確に、Microsoft.EntityFrameworkCore.Design.dll EF Core のすべてのデザイン時ロジックが含まれています。 すべてのエントリ ポイントは、OperationExecutor クラス内にあります。 このクラス (MarshallByRefObject、入れ子になった型など) の設計の奇妙な部分の多くは、.NET Framework 上の AppDomain 間でそれを呼び出す必要から生じるものです。 この要件が削除された場合は、多くの作業が簡略化される可能性があります。 すべての署名は、ツールとの前方互換性と後方互換性の両方を実現するために、柔軟に型付けされています。 さまざまなバージョンのツールを使用して、異なるバージョンの EF を使用してプロジェクトを呼び出すことができることに注意してください。

Executor に加えて、DbContextActivator も、このアセンブリのもう 1 つの重要な型です。 これは、デザイン時にユーザーの DbContext をインスタンス化するために、ASP.NET Web Tools コンポーネントの一部で使用されます。

DbContext の作成

特定のデザイン時ロジックを実行する前に、通常は DbContext インスタンスが必要です。 ユーザーは、DbContext に単純または完全修飾の大文字と小文字を区別しない型名を指定できます。または、DbContext 型が 1 つだけの場合は指定できません。 どちらの方法でも、1 つに絞り込む前に、すべての DbContext 型を検出する必要があります。 DbContext 型を検出するためのロジックは、DbContextOperationsの FindContextTypes メソッドにあります。

さまざまなソースを使用して DbContext 型を検索します。

  • スタートアップ アセンブリ内の IDesignTimeDbContextFactory<T> 実装による参照。
  • アプリケーションのサービスプロバイダーにDbContextが追加されました。 すべてのコンテキスト型の一覧を取得するには、すべてのコンテキストが DbContextOptions として登録され、ContextType プロパティを参照します。 (アプリケーション サービス プロバイダーを取得する方法については、以下を参照してください)。
  • スタートアップ アセンブリとターゲット アセンブリの両方で DbContext から派生した型

また、型をインスタンス化するさまざまな方法も使用します。 ここでは、優先順位の順に示します。

  1. IDesignTimeDbContextFactory<T> 実装の使用
  2. アプリケーション サービス プロバイダーから IDbContextFactory<T> を使用する
  3. ActivatorUtilities.CreateInstance の使用

アプリケーション サービスの検索

実行時の動作に対する最も忠実性が高い場合は、アプリケーション サービス プロバイダーから直接 DbContext インスタンスを取得しようとします。 このロジックは、ASP.NET Core ツールと共有します。 これは、GitHub の dotnet/runtime プロジェクトの一部として、Microsoft.Extensions.HostFactoryResolver ディレクトリの下に保持されます。

簡単に言うと、使用する戦略の一部を次に示します。

  • アセンブリ エントリ ポイントの横にある BuildWebHost、CreateWebHostBuilder、または CreateHostBuilder という名前のメソッドを探します
    • ホストをビルドし、Services プロパティからサービスを取得する
  • アセンブリ エントリ ポイントを呼び出す
    • ホストの構築中にサービスをインターセプトし、実際にホストを開始する前に終了する

デザイン時サービス

アプリケーション サービスと内部 DbContext サービスに加えて、デザイン時サービスの 3 番目のセットがあります。 これらは、実行時には必要ないため、内部サービス プロバイダーには追加されません。 デザイン時サービスは、DesignTimeServicesBuilder によって構築されます。 2 つの主要なパスがあります。1 つはコンテキスト インスタンスを持ち、1 つはなしです。 「なし」というオプションは、主に新しい DbContext を作成する時に使用されます。 ここでは、ユーザー、プロバイダー、拡張機能がサービスをオーバーライドおよびカスタマイズできるようにするための拡張ポイントがいくつかあります。

ユーザーは、スタートアップ アセンブリに IDesignTimeServices の実装を追加することで、サービスをカスタマイズできます。

プロバイダーは、DesignTimeProviderServices 属性をアセンブリに追加することで、サービスをカスタマイズできます。 これは、IDesignTimeServices の実装を指しています。

拡張機能では、ターゲット アセンブリまたはスタートアップ アセンブリに DesignTimeServicesReference 属性を追加することで、サービスをカスタマイズできます。 属性でプロバイダーが指定されている場合、そのプロバイダーが使用されている場合にのみ追加されます。

ログ記録と例外

DbContext をインスタンス化した後、そのログをツールの出力に接続します。 これにより、ランタイムから出力を生成できます。 未処理の例外も出力に書き込まれます。 例外の種類 OperationException という特殊なタイプがあり、これをスローすることでツールを円滑に終了し、スタックトレースなしで簡単なエラーメッセージを表示できます。