この記事では、DbContext インスタンスの初期化と構成の基本的なパターンについて説明します。
警告
この記事では、ユーザーの認証を必要としないローカル データベースを使用します。 運用アプリでは、使用可能な最も安全な認証フローを使用する必要があります。 デプロイされたテスト アプリと運用アプリの認証の詳細については、「セキュリティで保護された認証フロー」をご覧ください。
DbContext の有効期間
DbContext の有効期間は、インスタンスが作成された時点で開始し、インスタンスが破棄された時点で終了します。
DbContext インスタンスは、"単一" の作業単位で使用するように設計されています。 これは、DbContext インスタンスの有効期間が通常は非常に短いことを意味します。
ヒント
上のリンクから Martin Fowler 氏の言葉を引用します。「作業単位によって、データベースに影響する可能性があるビジネス トランザクションの間に実行した処理がすべて追跡されます。 完了時に、作業の結果としてデータベースを変更するために何を実行する必要があるかがすべて示されます。」
Entity Framework Core (EF Core) を使用する場合の一般的な作業単位には、次のものがあります。
-
DbContextインスタンスが作成されます。 - コンテキストによってエンティティ インスタンスが追跡されます。 エンティティは次の場合に追跡されます。
- ビジネス ルールを実装するために、必要に応じて追跡対象エンティティに変更が加えられます。
- SaveChanges または SaveChangesAsync が呼び出されます。 行われた変更が EF Core によって検出されてデータベースに書き込まれます。
-
DbContextインスタンスが破棄されます。
重要
- 使用後に DbContext を破棄することが重要です。 これにより、次の内容が保証されます。
- アンマネージド リソースは解放されます。
- イベントまたはその他のフックは登録解除されます。 登録を解除すると、インスタンスが参照されたままのときにメモリ リークが防止されます。
- DbContext は スレッド セーフでないです。 スレッド間でコンテキストを共有しないでください。 コンテキスト インスタンスの使用を継続する前に、すべての非同期呼び出しを必ず待機するようにしてください。
- EF Core コードによって発生する InvalidOperationException により、コンテキストが復旧不能な状態になる可能性があります。 このような例外はプログラムのエラーを示すものであり、回復が行われるようには設計されていません。
ASP.NET Core の依存関係の挿入における DbContext
多くの Web アプリケーションでは、各 HTTP 要求は 1 つの作業単位に対応します。 このため、コンテキストの有効期間を要求のものに結び付けることが、Web アプリケーションの適切な既定値になります。
ASP.NET Core アプリケーションは、依存関係の挿入を使用して構成されます。
Program.csでAddDbContextを使用して、EF Core をこの構成に追加できます。 次に例を示します。
var connectionString =
builder.Configuration.GetConnectionString("DefaultConnection")
?? throw new InvalidOperationException("Connection string"
+ "'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
上記のコードは、ApplicationDbContextのサブクラスであるDbContextを、ASP.NET Core アプリ サービス プロバイダーのスコープサービスとして登録します。 サービス プロバイダーは、依存関係挿入コンテナーとも呼ばれます。 コンテキストは、SQL Server データベース プロバイダーを使用するように構成され、ASP.NET Core 構成から接続文字列を読み取。
ApplicationDbContext クラスによって、DbContextOptions<ApplicationDbContext> パラメーターが指定されたパブリック コンストラクターが公開される必要があります。 これは、AddDbContext からのコンテキスト構成を DbContext に渡す方法です。 次に例を示します。
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
ApplicationDbContext は、コンストラクターの挿入を使用して、ASP.NET Core コントローラーまたはその他のサービスで使用できます。
public class MyController
{
private readonly ApplicationDbContext _context;
public MyController(ApplicationDbContext context)
{
_context = context;
}
}
最終的な結果は、要求ごとに作成され、要求の終了時に破棄される前に作業単位を実行するためにコントローラーに渡される、ApplicationDbContext インスタンスです。
構成オプションの詳細については、この記事を読み進めてください。 詳細については、「ASP.NET Core の Dependency インジェクション を参照してください。
"new" を使用した基本的な DbContext の初期化
DbContext インスタンスは、C# で new を使用して構築できます。 構成を実行するには、OnConfiguring メソッドをオーバーライドするか、またはコンストラクターにオプションを渡します。 次に例を示します。
public class ApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
}
}
また、このパターンを使用すると、DbContext コンストラクターを介して接続文字列のような構成を簡単に渡すことができます。 次に例を示します。
public class ApplicationDbContext : DbContext
{
private readonly string _connectionString;
public ApplicationDbContext(string connectionString)
{
_connectionString = connectionString;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_connectionString);
}
}
または、DbContextOptionsBuilder を使用して DbContextOptions オブジェクトを作成し、それを DbContext コンストラクターに渡すこともできます。 これにより、依存関係の挿入用に構成された DbContext も明示的に構築することができます。 たとえば、上述の ASP.NET Core Web アプリ用に定義された ApplicationDbContext を使用する場合は、次のようになります。
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
DbContextOptions を作成し、コンストラクターを明示的に呼び出すことができます。
var contextOptions = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0")
.Options;
using var context = new ApplicationDbContext(contextOptions);
DbContext ファクトリを使用する
一部のアプリケーションの種類 (ASP.NET Core Blazor など) では、依存関係の挿入が使用されますが、目的の DbContext の有効期間に合わせたサービス スコープが作成されるわけではありません。 このような調整が行われる場合であっても、このスコープ内で複数の作業単位がアプリケーションによって実行されなければならない場合があります。 たとえば、1 つの HTTP 要求内に複数の作業単位がある場合です。
このような場合、AddDbContextFactory を使用して、DbContext インスタンスを作成するためのファクトリを登録できます。 次に例を示します。
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextFactory<ApplicationDbContext>(
options => options.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0"));
}
ApplicationDbContext クラスによって、DbContextOptions<ApplicationDbContext> パラメーターが指定されたパブリック コンストラクターが公開される必要があります。 これは、上述の従来の ASP.NET Core に関するセクションで使用したパターンと同じものです。
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
DbContextFactory ファクトリはその後、コンストラクターの挿入を介して他のサービスで使用できるようになります。 次に例を示します。
private readonly IDbContextFactory<ApplicationDbContext> _contextFactory;
public MyController(IDbContextFactory<ApplicationDbContext> contextFactory)
{
_contextFactory = contextFactory;
}
挿入されたファクトリはその後、サービス コード内で DbContext インスタンスを構築するために使用できます。 次に例を示します。
public async Task DoSomething()
{
using (var context = _contextFactory.CreateDbContext())
{
// ...
}
}
なお、この方法で作成された DbContext インスタンスは、アプリケーションのサービス プロバイダーには管理 "されず"、従ってアプリケーションによって破棄される必要があります。
Blazor での EF Core の使用に関する詳細については、「ASP.NET Core Blazor Server と Entity Framework Core」を参照してください。
DbContextOptions
すべての DbContext 構成の開始地点は、DbContextOptionsBuilder です。 このビルダーを入手するには、次の 3 つの方法があります。
-
AddDbContextおよび関連するメソッド -
OnConfiguring -
newの使用による明示的な構築
これらのそれぞれの例については、これまでのセクションに示されています。 ビルダーの取得元に関係なく、同じ構成を適用できます。 また、コンテキストの構築方法に関係なく、常に OnConfiguring が呼び出されます。 これは、OnConfiguring が使用されている場合でも、AddDbContext を使用して追加の構成を実行できることを意味します。
データベースプロバイダーの構成
各 DbContext インスタンスは、1 つのデータベース プロバイダーのみを使用するように構成する必要があります
DbContext サブタイプの異なるインスタンスは、異なるデータベース プロバイダーで使用できますが、1 つのインスタンスで使用できるのは 1 つだけです)。データベース プロバイダーは、特定の Use* 呼び出しを使用して構成されます。 たとえば、SQL Server データベース プロバイダーを使用するには、次のようにします。
public class ApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
}
}
これらの Use* メソッドは、データベース プロバイダーによって実装される拡張メソッドです。 これは、拡張メソッドを使用する前に、データベース プロバイダーの NuGet パッケージをインストールする必要があることを意味します。
ヒント
拡張メソッドは EF Core データベース プロバイダーによって広く使用されます。 メソッドが見つからないことがコンパイラによって示された場合は、プロバイダーの NuGet パッケージがインストールされていることと、コードに using Microsoft.EntityFrameworkCore; が記述されていることを確認してください。
次の表に、一般的なデータベース プロバイダーの例を示します。
| データベース システム | 構成例 | NuGet パッケージ |
|---|---|---|
| SQL Server または Azure SQL | .UseSqlServer(connectionString) |
Microsoft.EntityFrameworkCore.SqlServer |
| Azure Cosmos DB | .UseCosmos(connectionString, databaseName) |
Microsoft.EntityFrameworkCore.Cosmos |
| SQLite | .UseSqlite(connectionString) |
Microsoft.EntityFrameworkCore.Sqlite |
| EF Core In-Memory データベース | .UseInMemoryDatabase(databaseName) |
Microsoft.EntityFrameworkCore.InMemory |
| PostgreSQL* | .UseNpgsql(connectionString) |
Npgsql.EntityFrameworkCore.PostgreSQL |
| MySQL/MariaDB* | .UseMySql(connectionString) |
Pomelo.EntityFrameworkCore.MySql |
| Oracle* | .UseOracle(connectionString) |
Oracle.EntityFrameworkCore |
* Microsoft はこれらのデータベース プロバイダーを出荷していません。 データベース プロバイダーの詳細については、「データベース プロバイダー」を参照してください。
警告
EF Core インメモリ データベースは、運用環境で使用するように設計されていません。 また、テスト用にも最適な選択肢ではない場合があります。 詳細については、「EF Core を使用するコードのテスト」を参照してください。
EF Core で接続文字列を使用する方法の詳細については、「接続文字列」を参照してください。
データベース プロバイダーに固有の省略可能な構成は、追加のプロバイダー固有のビルダーで実行されます。 たとえば、EnableRetryOnFailure を使用して、Azure SQL に接続するときに接続の回復性の再試行を構成します。
public class ApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=Test",
providerOptions => { providerOptions.EnableRetryOnFailure(); });
}
}
ヒント
SQL Server と Azure SQL には、同じデータベース プロバイダーが使用されます。 ただし、SQL Azure に接続するときは接続の回復性を使用することをお勧めします。
プロバイダー固有の構成の詳細については、「データベース プロバイダー」を参照してください。
その他の DbContext の構成
その他の DbContext の構成は、Use* 呼び出しの前または後のいずれかにチェーンすることができます (どちらでも違いはありません)。 たとえば、機密データのログ記録を有効にするには、次のようにします。
public class ApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.EnableSensitiveDataLogging()
.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
}
}
次の表に、DbContextOptionsBuilder で呼び出される一般的なメソッドの例を示します。
| DbContextOptionsBuilder メソッド | 動作内容 | 詳細情報 |
|---|---|---|
| UseQueryTrackingBehavior | クエリの既定の追跡動作が設定されます | クエリの追跡動作 |
| LogTo | EF Core ログを取得するシンプルな方法 | ログ、イベント、診断 |
| UseLoggerFactory |
Microsoft.Extensions.Logging ファクトリが登録されます |
ログ、イベント、診断 |
| EnableSensitiveDataLogging | 例外とログにアプリケーション データが含められます | ログ、イベント、診断 |
| EnableDetailedErrors | より詳細なクエリ エラー (パフォーマンスの低下と引き換え) | ログ、イベント、診断 |
| ConfigureWarnings | 警告やその他のイベントに対して無視するかスローするかを選択します | ログ、イベント、診断 |
| AddInterceptors | EF Core インターセプターが登録されます | ログ、イベント、診断 |
| EnableServiceProviderCaching | 内部サービス プロバイダーのキャッシュを制御します | サービス プロバイダーのキャッシュ |
| UseMemoryCache | EF Core で使用されるメモリ キャッシュを構成します | メモリ キャッシュの統合 |
| UseLazyLoadingProxies | 遅延読み込みのために動的プロキシが使用されます | 遅延読み込み |
| UseChangeTrackingProxies | 変更追跡のために動的プロキシが使用されます | 近日公開予定... |
注意
UseLazyLoadingProxies と UseChangeTrackingProxies は、Microsoft.EntityFrameworkCore.Proxies NuGet パッケージの拡張メソッドです。 この種の ".UseXXX()" 呼び出しは、他のパッケージに含まれている EF Core の拡張機能を構成または使用するための推奨される方法です。
DbContextOptions と DbContextOptions<TContext>
DbContext を受け入れるほとんどの DbContextOptions サブクラスでは、ジェネリックの DbContextOptions<TContext> バリエーションを使用する必要があります。 次に例を示します。
public sealed class SealedApplicationDbContext : DbContext
{
public SealedApplicationDbContext(DbContextOptions<SealedApplicationDbContext> contextOptions)
: base(contextOptions)
{
}
}
これにより、複数の DbContext サブタイプが登録されている場合でも、特定の DbContext サブタイプの正しいオプションが依存関係の挿入から解決されるようになります。
ヒント
DbContext はシールされる必要はありませんが、シールは継承するように設計されていないクラスに関してそうするためのベスト プラクティスです。
ただし、DbContext サブタイプ自体が継承するよう意図されている場合は、非ジェネリックの DbContextOptions を受け取る保護されたコンストラクターを公開する必要があります。 次に例を示します。
public abstract class ApplicationDbContextBase : DbContext
{
protected ApplicationDbContextBase(DbContextOptions contextOptions)
: base(contextOptions)
{
}
}
これにより、複数の具象サブクラスが、それぞれの異なるジェネリックの DbContextOptions<TContext> インスタンスを使用してこの基本コンストラクターを呼び出すことができるようになります。 次に例を示します。
public sealed class ApplicationDbContext1 : ApplicationDbContextBase
{
public ApplicationDbContext1(DbContextOptions<ApplicationDbContext1> contextOptions)
: base(contextOptions)
{
}
}
public sealed class ApplicationDbContext2 : ApplicationDbContextBase
{
public ApplicationDbContext2(DbContextOptions<ApplicationDbContext2> contextOptions)
: base(contextOptions)
{
}
}
これは、DbContext から直接継承する場合とまったく同じパターンであることに注目してください。 つまり、これが理由で、非ジェネリックの DbContext が DbContextOptions コンストラクター自体によって受け入れられます。
インスタンス化されることと継承されることの両方が意図されている DbContext サブクラスの場合、両方の形式のコンストラクターを公開する必要があります。 次に例を示します。
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> contextOptions)
: base(contextOptions)
{
}
protected ApplicationDbContext(DbContextOptions contextOptions)
: base(contextOptions)
{
}
}
デザイン時の DbContext の構成
EF Core 移行用などの EF Core デザイン時ツールでは、アプリケーションのエンティティ型とそれらがデータベース スキーマにどのようにマップされるかに関する詳細情報を収集するため、DbContext 型の作業インスタンスを検出および作成できる必要があります。 このプロセスは、実行時に構成される場合と同様の方法で構成されるように DbContext をツールで簡単に作成できる限り、自動的に行われます。
DbContext に必要な構成情報を提供するパターンは実行時に動作しますが、デザイン時に DbContext を使用する必要があるツールは、限られた数のパターンでのみ機能します。 これらについては、デザイン時のコンテキストの作成に関する記事で詳しく説明されています。
DbContext のスレッド処理の問題を回避する
Entity Framework Core では、同じ DbContext インスタンス上での複数の並列操作の実行がサポートされていません。 これには、非同期クエリの並列実行と、複数のスレッドからの明示的な同時使用の両方が含まれます。 そのため、常にawait直ちに非同期呼び出しを行うか、並列実行される操作に対して個別のDbContextインスタンスを使用します。
DbContext インスタンスを同時に使用しようとしたことが EF Core によって検出されると、次のようなメッセージとともに InvalidOperationException が表示されます。
前の操作が完了する前に、このコンテキストで 2 つ目の操作が開始されました。 これは通常、異なるスレッドで DbContext の同じインスタンスが使用された場合に発生しますが、インスタンスのメンバーがスレッド セーフである保証はありません。
同時アクセスが検出されない場合は、未定義の動作、アプリケーションのクラッシュ、データの破損が発生する可能性があります。
同じ DbContext インスタンスに誤って同時にアクセスする原因として、一般に次のような間違いがあります。
非同期操作の落とし穴
EF Core で非同期メソッドを使用すると、非ブロッキング モードでデータベースにアクセスする操作を開始できます。 しかし、呼び出し元がこれらのメソッドのいずれかの完了を待機せず、DbContext に対して他の操作を実行しようとした場合、DbContext の状態は (非常に高い確率で) 破損する可能性があります。
常に EF Core の非同期メソッドを直ちに待機してください。
依存関係の挿入によって DbContext インスタンスを暗黙的に共有する
AddDbContext 拡張メソッドは、DbContext に スコープ付きライフタイム で型を登録します。
この場合、ある時点で個々のクライアント要求を実行するスレッドは 1 つしかなく、各要求が別個の依存関係挿入スコープ (したがって、別個の DbContext インスタンス) を取得するため、ほとんどの ASP.NET Core アプリケーションでは同時アクセスの問題は発生しません。 Blazor Server ホスティング モデルでは、Blazor ユーザー回線を維持するために 1 つの論理要求が使用されるため、既定の挿入スコープが使用されている場合は、ユーザー回線ごとにスコープ付きの DbContext インスタンスを 1 つだけ使用できます。
複数のスレッドを明示的に並列実行するコードでは、DbContext インスタンスが同時にアクセスされないようにする必要があります。
依存関係の挿入を使用してこれを実現するには、コンテキストをスコープ付きとして登録し、スレッドごとに (IServiceScopeFactory を使用して) スコープを作成するか、または (DbContext パラメーターを取る AddDbContext のオーバーロードを使用して) ServiceLifetime を一時的として登録します。
構成の設定に対する ConfigureDbContext
注意
このセクションでは、主に再利用可能なライブラリとコンポーネントを対象とした EF Core の中間レベルの使用について説明します。 ほとんどのアプリケーションでは、この記事で前述した AddDbContextFactory パターンを使用する必要があります。
EF Core 9.0 以降では、ConfigureDbContextを使用して、DbContext呼び出しの前または後にAddDbContextに追加の構成を適用できます。 これは、再利用可能なコンポーネントまたはテストで競合しない構成を作成する場合に特に便利です。
ConfigureDbContext の基本的な使用方法
ConfigureDbContext を使用すると、プロバイダー構成全体を置き換えることなく、再利用可能なライブラリまたはコンポーネントに構成を追加できます。
var services = new ServiceCollection();
services.ConfigureDbContext<BlogContext>(options =>
options.EnableSensitiveDataLogging()
.EnableDetailedErrors());
services.AddDbContext<BlogContext>(options =>
options.UseInMemoryDatabase("BasicExample"));
接続文字列のないプロバイダー固有の構成
プロバイダー固有の構成を適用するには、接続文字列を指定せずにプロバイダー固有の構成方法を使用できます。 SQL Server プロバイダーには、この場合の ConfigureSqlEngine も含まれています。 詳細については、 SQL Server 固有のバッチ処理動作を 参照してください。
var services = new ServiceCollection();
services.ConfigureDbContext<BlogContext>(options =>
options.UseSqlServer(sqlOptions =>
sqlOptions.EnableRetryOnFailure()));
services.AddDbContext<BlogContext>(options =>
options.UseSqlServer("connectionString"));
ConfigureDbContext と AddDbContext の優先順位
ConfigureDbContextとAddDbContextの両方を使用する場合、またはこれらのメソッドを複数呼び出す場合は、メソッドが呼び出される順序で構成が適用され、後で競合するオプションの呼び出しが優先されます。
競合しないオプション (ログ記録、インターセプター、その他の設定の追加など) の場合、すべての構成が一緒に構成されます。
var services = new ServiceCollection();
services.ConfigureDbContext<BlogContext>(options =>
options.LogTo(Console.WriteLine));
services.AddDbContext<BlogContext>(options =>
options.UseInMemoryDatabase("CompositionExample"));
services.ConfigureDbContext<BlogContext>(options =>
options.EnableSensitiveDataLogging());
競合するオプションの場合は、最後の構成が優先されます。 この動作の変更の詳細については、 EF Core 8.0 の破壊的 変更を参照してください。
注意
別のプロバイダーを構成すると、以前のプロバイダー構成は削除されません。 これにより、コンテキストを作成するときにエラーが発生する可能性があります。 プロバイダーを完全に置き換えるには、コンテキスト登録を削除して再度追加するか、新しいサービス コレクションを作成する必要があります。
さらに読む
.NET