1. ホーム
  2. c#

[解決済み] [Solved] Webリクエストごとに1つのDbContext...なぜ?

2022-03-24 22:10:24

質問

Entity Frameworkの設定方法を説明した記事をたくさん読みました。 DbContext 様々なDIフレームワークを使用して、HTTPウェブリクエストごとに1つだけ作成され使用されるようにする。

そもそも、なぜこれが良いのでしょうか?この方法を使うことで、どんなメリットがあるのでしょうか?これが良いアイデアとなる特定の状況があるのでしょうか?をインスタンス化するときにできないことで、この手法でできることはありますか? DbContext をリポジトリメソッド呼び出しごとに実行できますか?

解決方法は?

<ブロッククオート

注:この回答は、Entity Frameworkの DbContext しかし のような、あらゆる種類の作業単位の実装に適用できます。 LINQ to SQLの DataContext と、NHibernateの ISession .

まず、Ian の意見に従います。 DbContext をアプリケーション全体に使用するのは、バッドアイディアです。これが意味を持つ唯一の状況は、シングルスレッド・アプリケーションと、その単一のアプリケーション・インスタンスによってのみ使用されるデータベースがある場合です。その場合 DbContext はスレッドセーフではなく、また DbContext を必要な人に入れることができます。(なぜ単一の DbContext -を、あるいはスレッドごとのコンテキストを使用するのはよくありません。 この回答 ).

まず最初に言っておくと DbContext しかし、通常、このような作業単位のインスタンスは、特定のスコープ内で1つだけ持つようにしたいものです。ウェブアプリケーションでは、ウェブリクエストの境界でそのようなスコープを定義することが実用的です。したがって、Per Web Requestライフスタイルとなります。これにより、一連のオブジェクトを同じコンテキストで動作させることができます。言い換えれば、それらは同じビジネストランザクションの中で操作されます。

もし、一連の操作を同じコンテキスト内で動作させるという目的がないのであれば、その場合はtransient lifestyleで問題ありませんが、いくつか注意しなければならない点があります。

  • すべてのオブジェクトはそれ自身のインスタンスを取得するため、システムの状態を変更するすべてのクラスは、そのインスタンスに対して DbContext (そうでなければ、変更が失われてしまう)。これはコードを複雑にし、コードに2つ目の責任(コンテキストを制御する責任)を追加することになります。 単一責任の原則 .
  • エンティティ[ロードとセーブは _context.SaveChanges() なぜなら、他のクラスのコンテキストインスタンスで使用することができないからです。これはコードを非常に複雑にします。なぜなら、それらのエンティティが必要になったとき、idによってそれらを再びロードする必要があり、パフォーマンスの問題を引き起こす可能性もあるからです。
  • 以来 DbContext を実装しています。 DbContext しかし、作成されたインスタンスをすべて破棄したい場合もあることでしょう。これを行う場合、基本的に2つの選択肢があります。を呼び出した直後に、同じメソッドでそれらのインスタンスを破棄する必要があります。 IDisposable しかし、この場合、ビジネスロジックは外部から渡されたオブジェクトの所有権を取得することになります。2つ目の選択肢は、Httpリクエストの境界で生成されたすべてのインスタンスを破棄することですが、この場合でも、これらのインスタンスを破棄する必要があるときにコンテナに知らせるために、何らかのスコープが必要です。

もう一つの選択肢は ない を注入します。 context.SaveChanges() を全く使用しません。その代わりに DbContext のインスタンスを作成することができます(以前はこの方法を使用していました)。こうすることで、ビジネスロジックが明示的にコンテキストを制御することができます。以下のような感じでしょうか。

DbContextFactory

の寿命を管理することができます。 public void SomeOperation() { using (var context = this.contextFactory.CreateNew()) { var entities = this.otherDependency.Operate( context, "some value"); context.Entities.InsertOnSubmit(entities); context.SaveChanges(); } } を明示的に設定することができ、その設定も簡単です。また、特定のスコープで単一のコンテキストを使用できるため、単一のビジネストランザクションでコードを実行したり、エンティティを受け渡しできるなど、明確な利点があります。 DbContext .

欠点は、その周りに DbContext メソッドからメソッドへ(これはメソッド・インジェクションと呼ばれる)。ある意味、この解決策は「スコープ付き」アプローチと同じですが、今はアプリケーションコード自体でスコープを制御している(そして何度も繰り返される可能性がある)ことに注意してください。作業単位の作成と廃棄を担当するのは、アプリケーションです。このため DbContext 依存関係グラフが構築された後に作成されるため、コンストラクタ注入は除外され、あるクラスから他のクラスにコンテキストを渡す必要がある場合は、メソッド注入に委ねる必要があります。

メソッド・インジェクションはそれほど悪いものではありませんが、ビジネスロジックがより複雑になり、より多くのクラスが関与するようになると、メソッドからメソッド、クラスからクラスへと受け渡す必要があり、コードが非常に複雑になります(私は過去にこれを経験したことがあります)。シンプルなアプリケーションであれば、この方法で問題ないでしょう。

このファクトリー・アプローチは大規模なシステムには不利なので、別のアプローチも有用です。 コンポジションルート は作業単位を管理します。これがご質問のスタイルです。

コンテナやインフラにこれを処理させることで、アプリケーションのコードはUoWインスタンスの作成、(オプションで)コミット、ディスポによって汚染されることがなくなり、ビジネスロジックをシンプルかつクリーンに保つことができます(単一の責任だけです)。このアプローチにはいくつかの難点があります。例えば、インスタンスのCommitとDisposeはどのように行うのでしょうか?

ユニットオブワークの廃棄は、ウェブリクエストの終了時に行うことができます。しかし、多くの人は 誤って をコミットする場でもあると想定している。しかし、アプリケーションのその時点では、作業単位が実際にコミットされるべきかどうかを確実に判断することはできません。例えば、ビジネス層のコードが例外を投げ、コールスタックの上位でキャッチされた場合、あなたは間違いなく しない コミットしたい。

本当の解決策は、ある種のスコープを明示的に管理することですが、今回はComposition Rootの内部でそれを行います。すべてのビジネスロジックを コマンド/ハンドラパターン そのため、各コマンドハンドラにラップできるデコレータを書くことができます。例

DbContext

これにより、このインフラストラクチャー・コードを一度だけ書く必要があることを保証します。しっかりした DI コンテナであれば、このようなデコレータを設定して、すべての class TransactionalCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand> { readonly DbContext context; readonly ICommandHandler<TCommand> decorated; public TransactionCommandHandlerDecorator( DbContext context, ICommandHandler<TCommand> decorated) { this.context = context; this.decorated = decorated; } public void Handle(TCommand command) { this.decorated.Handle(command); context.SaveChanges(); } } の実装を一貫して行うことができます。