Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【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リクエストパイプライン」の外でスコープ付きサービスを使いたい場合である。

例えば、バックグラウンドで定期的に処理を実行するサービス(IHostedServiceBackgroundServiceと呼ばれるもの)、特定の時刻に実行されるスケジュールされたジョブ、あるいは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リクエストの枠を超えて、バックグラウンド処理や他の非同期処理でスコープ付きサービスを安全かつ効率的に利用する際に、このパターンを適用することで、アプリケーションの堅牢性と保守性を高めることができる。システムエンジニアとして、このような設計パターンを理解し活用することは、高品質なソフトウェア開発において非常に重要だ。

関連コンテンツ

関連IT用語

関連ITニュース