【ITニュース解説】Understanding `IServiceScopeFactory.CreateScope()` in .NET and When to Use It
2025年09月26日に「Dev.to」が公開したITニュース「Understanding `IServiceScopeFactory.CreateScope()` in .NET and When to Use It」について初心者にもわかりやすく解説しています。
ITニュース概要
.NETのDIで、DB接続など「Scoped」なサービスはHTTPリクエストごとに作られる。バックグラウンド処理などリクエスト外で使うとエラーになる。`IServiceScopeFactory.CreateScope()`は、手動でDIスコープを作り、その中でScopedサービスを安全に利用・自動破棄する仕組みだ。
ITニュース解説
システム開発において、特に.NETのようなフレームワークでアプリケーションを構築する際、私たちは「依存関係の注入(Dependency Injection、通称DI)」という強力な仕組みを利用する。DIは、コンポーネントが互いにどのように連携するかを柔軟に定義し、コードのテストのしやすさや保守性を高めるために不可欠な技術だ。しかし、このDIを扱う上で、特定の状況下で多くの開発者が直面する少し複雑な問題がある。それは、特定のライフタイムを持つサービスを、通常のアプリケーションのフローとは異なる場所で安全に利用する方法に関する問題だ。この問題を解決するために登場するのが、IServiceScopeFactory.CreateScope()というメソッドである。
ASP.NET Coreアプリケーションでは、さまざまなサービスがDIコンテナに登録され、必要に応じて提供される。これらのサービスには、それぞれ「ライフタイム」と呼ばれる生存期間が設定されている。主なライフタイムは以下の三つだ。
一つ目は「シングルトン(Singleton)」サービスである。これはアプリケーション全体で一度だけ作成され、その後はずっと同じインスタンスが使い続けられる。アプリケーションの起動から終了まで存在し、すべてのリクエストやコンポーネントで共有されるべきデータや機能に適している。例えば、アプリケーションの設定情報を保持するサービスなどがこれに該当する。
二つ目は「スコープ付き(Scoped)」サービスである。これは特定の「スコープ」内で一度だけ作成され、そのスコープが終了すると破棄される。ASP.NET CoreのWebアプリケーションでは、HTTPリクエストごとに一つのスコープが自動的に作成される。つまり、ユーザーがWebサイトにアクセスしてページを要求するたびに、そのリクエスト専用の新しいスコープが始まり、そのスコープ内でスコープ付きサービスが初めて要求されたときに作成される。リクエストが処理され、応答が返されると、そのスコープも終了し、関連するスコープ付きサービスは適切に破棄される。データベースへの接続を管理するDbContextのようなサービスは、典型的にはスコープ付きとして登録される。これは、一つのリクエスト内で一貫したデータベース操作を行い、リクエストが終われば接続を閉じる、という運用が理想的だからだ。
三つ目は「トランジェント(Transient)」サービスである。これは要求されるたびに新しいインスタンスが作成される。例えば、一時的な計算を行うユーティリティサービスや、状態を持たない軽量なオブジェクトなどに使われる。
通常、Webアプリケーションのコントローラーやミドルウェア内部でサービスを利用する際には、これらのライフタイムを意識する必要はほとんどない。ASP.NET CoreがHTTPリクエストごとに自動的にスコープを作成し、スコープ付きサービスを適切に管理してくれるからだ。しかし、問題が発生するのは、この「通常のHTTPリクエストパイプライン」の外でスコープ付きサービスを使いたい場合である。
例えば、バックグラウンドで定期的に処理を実行するサービス(IHostedServiceやBackgroundServiceと呼ばれるもの)、特定の時刻に実行されるスケジュールされたジョブ、あるいはWebサーバーを介さずにDIを活用したいコンソールアプリケーションなどがこれに該当する。これらの環境では、HTTPリクエストという明確なスコープの区切りが存在しないため、DIコンテナはスコープ付きサービスをどのように管理すれば良いか分からない。
もしBackgroundServiceのようなシングルトンサービスに、DbContextのようなスコープ付きサービスを直接注入しようとすると、問題が発生する。シングルトンサービスはアプリケーションの起動時に一度だけインスタンス化され、その後はずっと存在し続ける。一方、スコープ付きサービスは特定のスコープ内で一度だけ作成され、スコープ終了時に破棄されることを期待している。シングルトンサービスがスコープ付きサービスを保持し続けてしまうと、スコープ付きサービスが破棄されるタイミングが不明確になり、リソースリーク(メモリやデータベース接続が適切に解放されない問題)を引き起こす可能性が出てくる。DIコンテナはこのような矛盾したライフタイムの注入を許さず、通常はエラーを発生させる。
ここでIServiceScopeFactory.CreateScope()が解決策として登場する。IServiceScopeFactoryは、DIコンテナから「手動で」新しいDIスコープを作成するための機能を提供する。このメソッドを使って作成されたスコープは、通常のHTTPリクエストスコープと同様に機能する独立した環境だ。
具体的な流れは次のようになる。まず、IServiceScopeFactoryをシングルトンサービス(例えばBackgroundService)に注入する。そして、バックグラウンド処理の中で、スコープ付きサービスが必要になったタイミングで_scopeFactory.CreateScope()を呼び出す。これにより、新しいスコープが手動で作成される。この作成されたスコープの内部では、scope.ServiceProvider.GetRequiredService<T>()というメソッドを使って、安全にスコープ付きサービスを取得できる。このサービスは、作成された手動スコープのライフタイムに紐付けられるため、外部のシングルトンサービスのライフタイムとは干渉しない。最も重要なのは、この手動で作成したスコープは、usingステートメントと組み合わせて使うことで、スコープが終了する際に、そのスコープ内で取得されたすべてのスコープ付きサービスが自動的に適切に破棄される点である。これにより、リソースリークの心配なく、独立した環境でスコープ付きサービスを利用できるようになる。
具体的な使用例をBackgroundServiceで見てみよう。
1public class MyBackgroundService : BackgroundService 2{ 3 private readonly IServiceScopeFactory _scopeFactory; 4 5 public MyBackgroundService(IServiceScopeFactory scopeFactory) 6 { 7 _scopeFactory = scopeFactory; // IServiceScopeFactoryをコンストラクタで注入する 8 } 9 10 protected override async Task ExecuteAsync(CancellationToken stoppingToken) 11 { 12 while (!stoppingToken.IsCancellationRequested) // アプリケーション停止信号がない限りループ 13 { 14 // ここで新しいDIスコープを手動で作成する 15 using (var scope = _scopeFactory.CreateScope()) 16 { 17 // 作成したスコープのServiceProviderを使って、スコープ付きサービスを取得する 18 // ApplicationDbContextは通常、スコープ付きサービスとして登録されている 19 var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); 20 21 // 取得したDbContextを安全に利用する 22 var users = await dbContext.Users.ToListAsync(); 23 // 例: users.ForEach(u => Console.WriteLine(u.Name)); 24 } // usingブロックを抜ける際に、scopeおよびその中で取得されたdbContextが自動的に破棄される 25 26 await Task.Delay(10000, stoppingToken); // 10秒待機 27 } 28 } 29}
このコードでは、MyBackgroundServiceがコンストラクタでIServiceScopeFactoryを受け取っている。これはMyBackgroundService自体がシングルトンとして登録されるため、ライフタイムの長いIServiceScopeFactoryを注入しても問題ない。ExecuteAsyncメソッドのループ内で、using (var scope = _scopeFactory.CreateScope())という記述がある。このusingブロックが非常に重要で、CreateScope()によって新しいDIスコープが生成され、このブロック内でそのスコープが有効となる。ブロックを抜ける際に、IDisposableインターフェースを実装しているscopeオブジェクトが自動的にDispose()メソッドを呼び出し、それに伴い、このスコープ内で取得されたApplicationDbContextのようなスコープ付きサービスも適切に解放される。これにより、バックグラウンド処理が定期的に実行されるたびに、新しいデータベース接続が確立され、処理完了後に適切に閉じられるという、健全なリソース管理が実現されるのだ。
IServiceScopeFactory.CreateScope()は、DIの基本的な仕組みを理解した上で、さらに一歩進んだ複雑なシナリオに対応するための強力なツールである。通常のWebリクエストの枠を超えて、バックグラウンド処理や他の非同期処理でスコープ付きサービスを安全かつ効率的に利用する際に、このパターンを適用することで、アプリケーションの堅牢性と保守性を高めることができる。システムエンジニアとして、このような設計パターンを理解し活用することは、高品質なソフトウェア開発において非常に重要だ。