Entity Frameworkの設定方法を説明する記事をたくさん読んでいます DbContext
て、さまざまなDIフレームワークを使用するHTTP Webリクエストごとに1つだけが作成および使用されるます。
そもそもなぜこれが良いアイデアなのでしょうか?このアプローチを使用すると、どのような利点がありますか?これが良い考えとなる特定の状況はありますか?この手法を使用して、DbContext
リポジトリメソッドの呼び出しごとにをインスタンス化するときに実行できないことはありますか?
Entity Frameworkの設定方法を説明する記事をたくさん読んでいます DbContext
て、さまざまなDIフレームワークを使用するHTTP Webリクエストごとに1つだけが作成および使用されるます。
そもそもなぜこれが良いアイデアなのでしょうか?このアプローチを使用すると、どのような利点がありますか?これが良い考えとなる特定の状況はありますか?この手法を使用して、DbContext
リポジトリメソッドの呼び出しごとにをインスタンス化するときに実行できないことはありますか?
回答:
注:この回答はEntity Frameworkについて説明し
DbContext
ていますがDataContext
、LINQ to SQL やNHibernateのようなあらゆる種類の作業単位の実装に適用できますISession
。
まず、Ianのエコーから始めましょうDbContext
。アプリケーション全体で1つにすることは悪い考えです。これが意味を持つ唯一の状況は、シングルスレッドアプリケーションと、その単一のアプリケーションインスタンスによってのみ使用されるデータベースがある場合です。DbContext
スレッドセーフとしているためではないDbContext
キャッシュデータ、それはかなりすぐに古くなります。これにより、複数のユーザー/アプリケーションがそのデータベースで同時に作業する場合に、あらゆる種類の問題が発生します(もちろん、これは非常に一般的です)。しかし、私はあなたがすでにそれを知っていて、なぜDbContext
それを必要とする人にの新しいインスタンス(つまり、一時的なライフスタイルを伴う)を注入しないのかを知りたいと思っています。(シングルDbContext
-またはスレッドごとのコンテキストでも-が悪い理由の詳細については、この回答をお読みください)。
まずは、 DbContext
一時的なものとしての可能性があることをおきますが、通常は、特定のスコープ内でそのような作業単位の単一のインスタンスが必要です。Webアプリケーションでは、そのようなスコープをWebリクエストの境界に定義することが実際的です。したがって、Webリクエストごとのライフスタイルです。これにより、オブジェクトのセット全体を同じコンテキスト内で動作させることができます。つまり、同じビジネストランザクション内で動作します。
一連の操作を同じコンテキスト内で動作させるという目標がない場合、その場合、一時的なライフスタイルは問題ありませんが、注意すべき点がいくつかあります。
_context.SaveChanges()
(そうしないと、変更が失われます)。これはコードを複雑にし、コードに2番目の責任(コンテキストを制御する責任)を追加する可能性があり、単一責任の原則に違反します。。DbContext
] エンティティは、別のクラスのコンテキストインスタンスでは使用できないため、そのようなクラスのスコープを離れないようにする必要があります。これらのエンティティが必要な場合は、IDでエンティティを再度読み込む必要があるため、コードが非常に複雑になる可能性があります。これにより、パフォーマンスの問題が発生する可能性もあります。DbContext
実装しているのでIDisposable
、作成されたすべてのインスタンスを破棄する必要があります。これを行う場合、基本的に2つのオプションがあります。を呼び出した直後に同じメソッドでそれらを破棄する必要がありますcontext.SaveChanges()
が、その場合、ビジネスロジックは外部から渡されるオブジェクトの所有権を取得します。2番目のオプションは、作成されたすべてのインスタンスをHttpリクエストの境界で破棄することですが、その場合でも、それらのインスタンスを破棄する必要があるときにコンテナに通知するために、何らかのスコープが必要です。別のオプションは、まったく注入しない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
メソッド間(メソッドインジェクションと呼ばれます)を渡す必要があることです。ある意味では、このソリューションは「スコープ」アプローチと同じですが、スコープはアプリケーションコード自体で制御されていることに注意してください(おそらく何度も繰り返されます)。作業単位の作成と廃棄を担当するのはアプリケーションです。以来DbContext
は依存関係グラフが作成された後に作成される、コンストラクターインジェクションは図外にあり、あるクラスから別のクラスにコンテキストを渡す必要がある場合は、メソッドインジェクションに従う必要があります。
メソッドインジェクションはそれほど悪くはありませんが、ビジネスロジックがより複雑になり、より多くのクラスが関与するようになると、メソッドからメソッドへ、クラスからクラスへと渡す必要があり、コードが非常に複雑になる可能性があります(これは過去)。単純なアプリケーションの場合、このアプローチで十分です。
欠点があるため、このファクトリーアプローチは大規模なシステムに対応しています。別のアプローチも役立ちます。これは、コンテナーまたはインフラストラクチャコード/ コンポジションルートが作業単位を管理できるようにするものです。これがあなたの質問のスタイルです。
コンテナーやインフラストラクチャがこれを処理できるようにすることで、UoWインスタンスを作成して(オプションで)コミットして破棄する必要がないため、アプリケーションコードが汚染されず、ビジネスロジックがシンプルかつクリーンに保たれます(単一の責任のみ)。このアプローチにはいくつかの困難があります。たとえば、インスタンスをコミットして破棄しましたか?
作業単位の破棄は、Webリクエストの最後に行うことができます。ただし、多くの人は、これが作業単位をコミットする場所でもあると誤って想定しています。ただし、アプリケーションのその時点では、作業単位が実際にコミットされているかどうかを確認することはできません。たとえば、ビジネスレイヤーのコードが呼び出しスタックの上位でキャッチされた例外をスローした場合、コミットする必要はありません。
実際の解決策は、ある種のスコープを明示的に管理することですが、今回はコンポジションルート内で管理します。コマンド/ハンドラーパターンの背後にあるすべてのビジネスロジックを抽象化すると、これを可能にする各コマンドハンドラーの周りにラップできるデコレーターを作成できます。例:
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();
}
}
これにより、このインフラストラクチャコードを1回だけ記述する必要があります。ソリッドDIコンテナーを使用すると、このようなデコレーターを構成ICommandHandler<T>
して、すべての実装を一貫した方法でラップすることができます。
CreateCommand<TEnity>
とジェネリックを作成できますCreateCommandHandler<TEntity> : ICommandHandler<CreateCommand<TEntity>>
(そして、更新と削除についても同じことを行い、単一のGetByIdQuery<TEntity>
クエリがありました)。それでも、このモデルがCRUD操作の有用な抽象化なのか、それとも複雑さを追加するだけなのかを自問する必要があります。それでも、このモデルを使用すると、クロスデコレーションの問題を(デコレーターを介して)簡単に追加できる可能性から利益を得る可能性があります。長所と短所を比較検討する必要があります。
TransactionCommandHandlerDecorator
ください。装飾クラスは、?たとえば、装飾されたクラスがInsertCommandHandler
クラスの場合、挿入操作をコンテキスト(EFのDbContext)にどのように登録できますか?
マイクロソフトによる2つの矛盾する推奨事項があり、多くの人々は完全に異なる方法でDbContextsを使用します。
リクエストがDbに関係のない多くのことを行っている場合、理由もなくDbContextが保持されるため、これらは互いに矛盾します。したがって、リクエストがランダムな処理が完了するのを待っている間だけ、DbContextを有効にしておくのは無駄です。
ルール1に従う多くの人々は、「リポジトリパターン」内にDbContextを持ち、データベースクエリごとに新しいインスタンスを作成するため、X * DbContextリクエストあたり
彼らはデータを取得し、コンテキストをできるだけ早く破棄します。これはによって考慮されるMANY許容練習人々 。これには、最小限の時間でdbリソースを占有するという利点がありますが、EFが提供するすべてのUnitOfWorkおよびCaching candyを明らかに犠牲にします。
DbContextの単一の多目的インスタンスを存続させると、キャッシュの利点が最大になりますが、DbContextはスレッドセーフではなく、各Web要求は独自のスレッドで実行されるため、要求ごとのDbContextは、それを維持できる最長です。
そのため、リクエストごとに1 Dbコンテキストを使用することに関するEFのチーム推奨は、WebアプリケーションではUnitOfWorkが1つのリクエスト内にあり、そのリクエストには1つのスレッドがあるという事実に明らかに基づいています。したがって、リクエストごとに1つのDbContextは、UnitOfWorkおよびCachingの理想的な利点のようです。
しかし、多くの場合、これは真実ではありません。私は別のUnitOfWorkをログに記録することを検討しているため、非同期スレッドでリクエスト後のログ記録に新しいDbContextを使用することは完全に許容可能です。
したがって、最後に、DbContextの存続期間がこれらの2つのパラメーターに制限されることが断られます。UnitOfWorkとスレッド
ここでは単一の答えではなく、実際に質問に答えます。OPは、シングルトン/アプリケーションごとのDbContext設計については尋ねず、(Web)リクエストごとの設計と、存在する可能性のある潜在的な利点について尋ねました。
Mehdiは素晴らしいリソースなので、私はhttp://mehdi.me/ambient-dbcontext-in-ef6/を参照します。
可能なパフォーマンスの向上。
各DbContextインスタンスは、データベースからロードするすべてのエンティティの第1レベルのキャッシュを維持します。主キーでエンティティにクエリを実行するときは常に、DbContextは、デフォルトでデータベースからクエリする前に、最初のレベルのキャッシュからエンティティを取得しようとします。データクエリのパターンによっては、複数の連続するビジネストランザクションで同じDbContextを再利用すると、DbContextの1次レベルキャッシュのおかげで、データベースクエリが少なくなる場合があります。
遅延読み込みを有効にします。
(ビューモデルやその他の種類のDTOを返すのではなく)サービスが永続エンティティを返し、それらのエンティティのレイジーロードを利用したい場合、それらのエンティティが取得されたDbContextインスタンスのライフタイムは、ビジネストランザクションの範囲。サービスメソッドが返す前に使用したDbContextインスタンスを破棄した場合、返されたエンティティのプロパティをレイジーロードする試みは失敗します(レイジーロードを使用するかどうかは別の議論であり、ここでは取り上げません。ここに)。Webアプリケーションの例では、通常、レイジーロードは、別のサービスレイヤーから返されるエンティティのコントローラーアクションメソッドで使用されます。その場合、
短所もあることに注意してください。そのリンクには、主題について読むための他の多くのリソースが含まれています。
他の誰かがこの質問に遭遇し、実際に質問に対応していない回答に夢中にならない場合に備えて、これを投稿するだけです。
これは、DbContextがスレッドセーフではないためだと思います。したがって、物事を共有することは決して良い考えではありません。
質問またはディスカッションで実際に扱われていないことの1つは、DbContextが変更をキャンセルできないことです。変更を送信することはできますが、変更ツリーをクリアすることはできないため、要求ごとのコンテキストを使用する場合、何らかの理由で変更を破棄する必要がある場合は運が悪かったです。
個人的には、必要に応じてDbContextのインスタンスを作成します。通常、必要に応じてコンテキストを再作成する機能を持つビジネスコンポーネントにアタッチされます。そうすれば、単一のインスタンスを強制的に実行させるのではなく、プロセスを制御できます。また、実際に使用されるかどうかに関係なく、コントローラーの起動ごとにDbContextを作成する必要はありません。その後、リクエストごとのインスタンスが必要な場合は、CTORで(DI経由または手動で)作成するか、必要に応じて各コントローラーメソッドで作成できます。個人的に私は通常、後者のアプローチをとって、実際に必要でないときにDbContextインスタンスを作成しないようにします。
それはあなたがそれをどの角度から見るかにもよる。私にとってリクエストごとのインスタンスは意味がありません。DbContextは本当にHttpリクエストに属していますか?行動の点では、それは間違った場所です。ビジネスコンポーネントは、Httpリクエストではなく、コンテキストを作成する必要があります。その後、必要に応じてビジネスコンポーネントを作成または破棄でき、コンテキストの存続期間について心配する必要はありません。
私は以前の意見に同意します。シングルスレッドアプリでDbContextを共有する場合は、より多くのメモリが必要になると言うのは良いことです。たとえば、AzureのWebアプリケーション(1つの余分な小さなインスタンス)にはさらに150 MBのメモリが必要で、1時間あたり約30人のユーザーがいます。
これが実際のサンプル画像です:アプリケーションは12PMにデプロイされています
シングルスレッドのシングルユーザーアプリケーションでさえ、シングルトンDbContextを使用しないもう1つの控えめな理由は、使用するIDマップパターンが原因です。つまり、クエリまたはIDを使用してデータを取得するたびに、取得したエンティティインスタンスがキャッシュに保持されます。次回同じエンティティを取得するときに、キャッシュされたエンティティのインスタンスが利用可能であれば、同じセッションで行った変更が反映されます。これは、SaveChangesメソッドが同じデータベースレコードの複数の異なるエンティティインスタンスで終了しないようにするために必要です。そうでなければ、コンテキストは何らかの形でそれらすべてのエンティティインスタンスからのデータをマージする必要があります。
問題である理由は、シングルトンのDbContextが時々爆弾になり、最終的にデータベース全体と.NETオブジェクトのオーバーヘッドをメモリにキャッシュする可能性があるためです。
.NoTracking()
拡張メソッドでLinqクエリのみを使用することにより、この動作を回避する方法があります。また、最近のPCには大量のRAMが搭載されています。しかし、通常、それは望ましい動作ではありません。
Entity Frameworkで特に注意すべきもう1つの問題は、新しいエンティティの作成、遅延読み込み、およびそれらの新しいエンティティ(同じコンテキストから)の組み合わせを使用する場合です。IDbSet.Create(vs new)を使用しない場合、そのエンティティが作成されたコンテキストから取得されたときに、そのエンティティの遅延読み込みは機能しません。例:
public class Foo {
public string Id {get; set; }
public string BarId {get; set; }
// lazy loaded relationship to bar
public virtual Bar Bar { get; set;}
}
var foo = new Foo {
Id = "foo id"
BarId = "some existing bar id"
};
dbContext.Set<Foo>().Add(foo);
dbContext.SaveChanges();
// some other code, using the same context
var foo = dbContext.Set<Foo>().Find("foo id");
var barProp = foo.Bar.SomeBarProp; // fails with null reference even though we have BarId set.