DbContextの登録方法 使い分けられていますか?
Entity Framework Core を使っていると、Program.cs にこんなコードを書いたことがある人は多いはずです。
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
しかし、あなたはこの AddDbContext をどれくらい理解して使っていますか?
実は、DbContext の登録方法にはこのほかにもAddDbContextPool、AddDbContextFactory、AddPooledDbContextFactoryなどが存在します。
そしてこれらは、アプリの構成・トラフィック量・ライフサイクル によって使い分ける必要があります。
「とりあえず AddDbContext にしておけば動くから」と思っていると、知らないうちにメモリ効率の悪化やスレッドスコープの不整合に悩まされることがあります。
本記事では、それぞれの登録方法の特徴と違いをざっくりと整理し、「どんな場面でどれを選ぶべきか?」を解説します。
認識違い等ございましたらご指摘いただけますと幸いです。
AddDbContext ― 最も基本的な登録方法
まずは最もよく使われる AddDbContext から見ていきましょう。
var connectionString =
builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString));
特徴
- スコープライフタイム(Scoped)で登録される
各HTTPリクエストごとに新しいDbContextインスタンスが生成され、リクエスト終了時に破棄されます。 - スレッドセーフではない
同じDbContextインスタンスを複数スレッドで共有しないよう注意。 - DI(依存性注入)に完全対応
Controller や Service にコンストラクタインジェクションで受け取るのが標準的な使い方です。
public class UserService
{
private readonly AppDbContext _db;
public UserService(AppDbContext db)
{
_db = db;
}
public async Task<List<User>> GetUsersAsync()
{
return await _db.Users.ToListAsync();
}
}
適しているケース
- 通常のWebアプリ
最も一般的。認証やCRUDなどに最適。 - シンプルなAPI
大きな負荷や並列性を求めない中小規模API。 - 開発初期段階
初期実装や検証段階でも扱いやすい。
- 1リクエスト内で長時間ロックを保持しないこと
長いトランザクションを扱うと、同一レコードを操作する他リクエストをブロックしてしまう可能性が高まります。 - バックグラウンドタスク(
IHostedServiceなど)では使用しないこと
AddDbContextで登録されたインスタンスは Scoped なので、HTTPリクエストのスコープ外では解決できません。
その場合は明示的にスコープを作成して解決するか、後述するAddDbContextFactory/AddPooledDbContextFactoryを使います。
明示的にスコープを作成する例
public class Worker : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
public Worker(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using (var scope = _serviceProvider.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// ここでDbContextを普通に使える
var pendingJobs = await db.Jobs
.Where(j => j.Status == JobStatus.Pending)
.ToListAsync(stoppingToken);
// ... 処理 ...
}
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
}
}
AddDbContextPool ― 高負荷環境でのパフォーマンス最適化
AddDbContext は安全で扱いやすい反面、大量のリクエストをさばく高トラフィック環境では毎回新しい DbContext を生成するコストが無視できません。
そんなときに有効なのが AddDbContextPool です。
builder.Services.AddDbContextPool<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")),
poolSize: 128); // プールサイズを指定することができる(デフォルトは1024)
特徴
-
DbContextインスタンスを使い回すプーリング機構を提供
内部的には、1度作成したDbContextを「リセット」して再利用することで、GCの負荷を軽減し、インスタンス生成コストを削減します。 - 登録ライフタイムは Scoped
補足
プール化された DbContext は、貸出(リクエスト開始時) → 返却(リクエスト終了時)という流れで再利用されます。
再利用時には内部状態(ChangeTracker やトランザクション情報)がリセットされるため、リクエストごとの独立性は保たれたまま性能を向上させることができます。
適しているケース
- 高トラフィックなWeb API
- ステートレスなCRUD処理
- GC負荷を抑えたい
- Scoped 登録ではあるものの、実質的には “Transient に近い振る舞い”
DIコンテナが「同一スコープ内で1インスタンスを保持」するのではなく、プールからその都度新しい(または再利用済み)インスタンスを貸し出します。そのため、スコープ内で同じインスタンスが共有される保証はありません。
AddDbContextFactory ― バックグラウンド処理で安全に使う
AddDbContext や AddDbContextPool は、基本的にHTTPリクエストのスコープ内で使うことを前提としています。
つまり、リクエスト外のスコープ(バックグラウンド処理やバッチ処理)では DbContext を安全に解決できません。
この制約を解消するのが AddDbContextFactory です。
builder.Services.AddDbContextFactory<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
特徴
-
DbContextそのものではなくIDbContextFactory<AppDbContext>をDIに登録(Singletonでスレッドセーフ)
このFactoryを使って、任意のタイミング・任意のスコープでDbContextを生成できるのが最大の利点です。
バックグラウンドサービスでの使用例
public class UserSyncService : BackgroundService
{
private readonly IDbContextFactory<AppDbContext> _factory;
public UserSyncService(IDbContextFactory<AppDbContext> factory)
{
_factory = factory;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await using var db = _factory.CreateDbContext();
var users = await db.Users.Where(u => !u.IsSynced).ToListAsync(stoppingToken);
// 同期処理など
...
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
}
}
}
適しているケース
- バックグラウンドジョブ(
IHostedService)
定期的なDBアクセスを行うジョブ処理 - メッセージキュー処理(SQS, SNSなど)
リクエスト外でDB更新が発生する非同期タスク - 短寿命トランザクション
必要なタイミングで都度コンテキストを生成・破棄したい場合
-
IDbContextFactoryはプールしない
毎回新しいDbContextを生成するため、プールによる性能向上はありません。 -
using/await usingが必須
明示的に破棄しないとコネクションリークの原因になります。
AddPooledDbContextFactory ― 柔軟性とパフォーマンスの両立
ここまでで紹介した AddDbContextPool と AddDbContextFactory、この2つのいいとこ取りを実現したのが AddPooledDbContextFactory です。
builder.Services.AddPooledDbContextFactory<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
AddPooledDbContextFactory は、Factoryパターンの柔軟性を維持しながら、DbContextのインスタンス再利用によるパフォーマンス最適化も同時に行います。
特徴
-
AddDbContextFactory同様、SingletonでIDbContextFactory<AppDbContext>が登録される - Factoryが「プールの管理者」となり、生成済みの
DbContextを再利用していく仕組み
適しているケース
- 高頻度のバックグラウンドジョブ
- 非同期イベント処理(Outboxなど)
AddDbContextFactoryと同じ用途だが性能を追求したい場合。 - 高負荷Webサービスのワーカー層
複数スレッドでDbContextを都度生成・破棄したい場面。
まとめ
ここまで見てきたように、DbContext の登録方法には4つの選択肢がありました。
| 登録方法 | 特徴 | 向いているシーン |
|---|---|---|
AddDbContext |
最も基本的。リクエスト単位でScoped。 | 通常のWebアプリや管理画面系サービス |
AddDbContextPool |
DbContextをプールして再利用。高パフォーマンス。 | 高トラフィックなAPIやステートレスなCRUD処理 |
AddDbContextFactory |
Factory経由で生成。スコープ外でも安全。 | バックグラウンドジョブやバッチ処理 |
AddPooledDbContextFactory |
Factory+Poolのハイブリッド。柔軟かつ高速。 | 非同期処理や高負荷環境での長期稼働サービス |
選択の指針
-
まずは
AddDbContextから始める
一番扱いやすく、ほとんどのWebアプリに十分。 -
パフォーマンスに課題を感じたら
AddDbContextPool
GCやメモリ使用量を減らせる。 -
HTTPリクエスト外の処理を行うなら
AddDbContextFactory
IHostedService、CLIツール、ジョブなどで安全。 -
柔軟性と性能を両立したいなら
AddPooledDbContextFactory
現代的な分散・非同期システムに最適。
DbContextの登録方法を理解し、アプリの性質に合わせて適切に選ぶこと。
それが、堅牢でパフォーマンスの高いASP.NET Coreアプリを作る第一歩です。