web-dev-qa-db-ja.com

MVCコアでDbContext Lifetimeを管理するにはどうすればよいですか?

ドキュメント から

Scopedライフタイムを使用して、Entity Frameworkコンテキストshouldをサービスコンテナーに追加する必要があります。上記のヘルパーメソッドを使用する場合、これは自動的に処理されます。 Entity Frameworkを使用するリポジトリは、同じライフタイムを使用する必要があります。

私は、処理しなければならない作業単位ごとに新しいContextを作成する必要があると常に考えていました。これは、ServiceAServiceBがあり、DbContextに異なるアクションを適用している場合、DbContextの異なるインスタンスを取得する必要があると考えてみましょう。

documentation は次のようになります。

  • Transientオブジェクトは常に異なります。新しいインスタンスがすべてのコントローラーとすべてのサービスに提供されます。

  • Scopedオブジェクトはリクエスト内では同じですが、リクエストごとに異なります

ServiceAServiceBに戻ると、Transientの方が適しているように思えます。

コンテキストはHttpRequestごとに1回だけ保存する必要があることを調査しましたが、実際にこれがどのように機能するか理解できません。

特に1つのサービスを見ると:

using (var transaction = dbContext.Database.BeginTransaction())
{
    //Create some entity
    var someEntity = new SomeEntity();
    dbContext.SomeEntity.Add(someEntity);

    //Save in order to get the the id of the entity
    dbContext.SaveChanges();

    //Create related entity
    var relatedEntity = new RelatedEntity
    {
        SomeEntityId = someEntity.Id
    };
    dbContext.RelatedEntity.Add(relatedEntity)
    dbContext.SaveChanges();
    transaction.Commit();
}

ここで、作成したばかりの別のエンティティに関連するエンティティのIDを取得するために、コンテキストを保存する必要があります。

同時に、別のサービスが同じコンテキストを更新できます。私が読んだことから、DbContextはスレッドセーフではありません。

この場合、Transientを使用する必要がありますか?なぜドキュメントが示唆しているのですか、私はshouldScopedを使用しますか?

フレームワークの重要な部分を見逃していますか?

17

他の人がすでに説明したように、shouldデータベースコンテキストのスコープ依存関係を使用して、適切に再利用されるようにします。並行性については、データベースに非同期でクエリを実行できるため、実際のスレッドは必要ない場合があることに注意してください。

needスレッド、つまりバックグラウンドワーカーを行う場合、それらはリクエストとは異なる有効期間を持つ可能性があります。そのため、これらのスレッドは、リクエストスコープから取得した依存関係をnot使用する必要があります。要求が終了し、その依存関係スコープが閉じられると、使い捨て可能な依存関係は適切に破棄されます。他のスレッドの場合、これは、依存関係がまだ必要であるにもかかわらず、依存関係が破棄される可能性があることを意味します。

代わりに、作成するすべてのスレッドに対して新しい依存関係スコープを明示的に開く必要があります。これを行うには、 IServiceScopeFactory を挿入し、 CreateScope を使用してスコープを作成します。結果のオブジェクトには、依存関係を取得できるサービスプロバイダーが含まれます。これは別のスコープであるため、データベースコンテキストなどのスコープ付き依存関係は、このスコープの存続期間中に再作成されます。

サービスロケーターパターンにならないようにするには、必要なすべての依存関係をまとめるスレッドが実行する1つの中央サービスを考慮する必要があります。スレッドはこれを行うことができます:

using (var scope = _scopeFactory.CreateScope())
{
    var service = scope.ServiceProvider.GetService<BackgroundThreadService>();
    service.Run();
}

BackgroundThreadServiceとそのすべての依存関係は、依存関係を受け取る一般的な依存関係注入方法に従うことができます。

23
poke

スコープライフタイムを使用する場合、ほとんどの場合、同時実行の問題に直面することはないと思います。投稿した例でも、現在のリクエストのサービスはその後呼び出されるため、同時実行性の問題はありません。 1つのHTTPリクエスト(スコープ)のコンテキストで2つ以上のサービスを並行して(可能ですが通常ではない)実行する場合を想像することさえできません。

ライフタイムは、データを保存するための単なる方法です(ここでは簡単に説明します)。人気のあるDIフレームワークのライフタイムマネージャーを見てください。それらはすべて同じように機能します。これは、使い捨てパターンを実装するオブジェクトのような辞書です。 Transientを使用すると、get objectメソッドは常にnullを返すため、DIは要求するたびに新しいインスタンスを作成します。 SingleInstanceはオブジェクトを静的な同時辞書のようなものに保存するため、コンテナはインスタンスを1回だけ作成し、既存のインスタンスを受け取ります。

有効範囲は通常、平均スコープオブジェクトが作成されたオブジェクトの格納に使用されます。 ASPネットパイプラインでは、通常はリクエストごとと同じ意味です(スコープがパイプラインを通過できるため)

短くするには-スコープを使用するだけで安全です。リクエストごとに呼び出すことができます。

私の説明では非常にシンプルにしようとしましたが、ソースコードを常に見て、必要に応じて一致する詳細を見つけることができます https://github.com/aspnet/DependencyInjection

2
Alex Lyalka