428

DbContextさまざまなDIフレームワークを使用して、HTTP Webリクエストごとに1つだけが作成および使用されるようにEntity Frameworkをセットアップする方法を説明する多くの記事を読んでいます。

そもそもなぜこれが良いアイデアなのですか?このアプローチを使用すると、どのような利点が得られますか? これが良い考えになる特定の状況はありますか? DbContextこの手法を使用してできることで、リポジトリ メソッド呼び出しごとに sをインスタンス化するときにできないことはありますか?

4

9 に答える 9

606

注:この回答はEntity FrameworkDbContextの.DataContextISession

Ian の言葉を借りて始めましょう:DbContextアプリケーション全体に単一のものを用意するのは悪い考えです。これが意味を持つ唯一の状況は、シングル スレッド アプリケーションと、その単一のアプリケーション インスタンスによってのみ使用されるデータベースがある場合です。はDbContextスレッドセーフではなく、DbContextデータをキャッシュするため、すぐに古くなります。これにより、複数のユーザー/アプリケーションがそのデータベースで同時に作業しているときに、あらゆる種類の問題が発生します (これはもちろん非常に一般的です)。しかし、あなたはすでにそれを知っていると思います.そして、それを必要とする人に新しいインスタンスを(つまり、一時的なライフスタイルで)注入しない理由を知りたいだけですDbContext. DbContext(単一の、またはスレッドごとのコンテキストでさえも悪い理由の詳細については、この回答を読んでください)。

まず、を一時的なものとして登録するDbContextこともできますが、通常は、特定のスコープ内でそのような作業単位の単一のインスタンスを保持する必要があります。Web アプリケーションでは、Web 要求の境界でこのようなスコープを定義することが実用的です。したがって、Per Web Request ライフスタイルです。これにより、一連のオブジェクト全体を同じコンテキスト内で動作させることができます。つまり、これらは同じビジネス トランザクション内で動作します。

一連の操作を同じコンテキスト内で動作させるという目標がない場合、その場合は一時的なライフスタイルで問題ありませんが、注意すべき点がいくつかあります。

  • すべてのオブジェクトが独自のインスタンスを取得するため、システムの状態を変更するすべてのクラスを呼び出す必要があります_context.SaveChanges()(そうしないと、変更が失われます)。これはコードを複雑にする可能性があり、コードに 2 番目の責任 (コンテキストを制御する責任) を追加する可能性があり、単一責任の原則に違反します。
  • [によってロードおよび保存された] エンティティDbContextは、別のクラスのコンテキスト インスタンスでは使用できないため、そのようなクラスのスコープを決して離れないようにする必要があります。これにより、コードが非常に複雑になる可能性があります。これらのエンティティが必要な場合は、id で再度読み込む必要があり、パフォーマンスの問題が発生する可能性があるからです。
  • DbContextが実装されているためIDisposable、作成されたすべてのインスタンスを破棄したい場合があります。これを行う場合、基本的に 2 つのオプションがあります。を呼び出した直後に同じメソッドでそれらを破棄する必要がありますcontext.SaveChanges()が、その場合、ビジネス ロジックは外部から渡されたオブジェクトの所有権を取得します。2 番目のオプションは、HTTP 要求の境界で作成されたすべてのインスタンスを破棄することですが、その場合でも、これらのインスタンスをいつ破棄する必要があるかをコンテナーに知らせるために、ある種のスコープが必要です。

別のオプションは、 a をまったく注入しないDbContextことです。代わりに、新しいインスタンスを作成できる a を注入し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.

欠点は、from メソッドをメソッドに渡さなければならないことです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 回記述するだけで済みます。ICommandHandler<T>堅実な DI コンテナーを使用すると、そのようなデコレーターを構成して、一貫した方法ですべての実装をラップすることができます。

于 2012-05-14T17:59:04.947 に答える
41

Microsoft による2 つの相反する推奨事項があり、多くの人が DbContexts を完全に異なる方法で使用しています。

  1. 推奨事項の 1 つは、「DbContext をできるだけ早く破棄する」 ことです。これは、DbContext を有効にすると、データベース接続などの貴重なリソースが占有されるためです。
  2. もう1 つは、リクエストごとに 1 つの DbContext が強く推奨されることを示しています

Request が Db とは関係のない多くのことを行っている場合、 DbContext は理由もなく保持されるため、これらは互いに矛盾します。したがって、リクエストがランダムな処理が完了するのを待っている間、DbContext を有効にしておくのは無駄です...

ルール 1に従う非常に多くの人々は、 "リポジトリ パターン"内に DbContext を持ち、データベース クエリごとに新しいインスタンスを作成するため、リクエストごとにX*DbContextが作成されます。

彼らはデータを取得し、コンテキストをできるだけ早く破棄します。これは、多くの人が容認できる慣行であると考えています。これには、最小限の時間でデータベース リソースを占有するという利点がありますが、明らかに、EF が提供しなければならないすべてのUnitOfWorkキャッシングキャンディーを犠牲にします。

DbContextの単一の多目的インスタンスを維持すると、キャッシュの利点が最大化されますが、DbContext はスレッド セーフではなく、各 Web 要求が独自のスレッドで実行されるため、要求ごとの DbContextが保持できる最長時間になります。

したがって、リクエストごとに 1 Db コンテキストを使用することに関する EF のチームの推奨事項は、Web アプリケーションでは UnitOfWork が 1 つのリクエスト内にある可能性が高く、そのリクエストには 1 つのスレッドがあるという事実に明らかに基づいています。したがって、リクエストごとに 1 つの DbContext は、UnitOfWork とキャッシングの理想的な利点のようなものです。

しかし、多くの場合、これは真実ではありません。別の UnitOfWork をログに記録することを検討しているため、非同期スレッドで要求後のログ記録用に新しい DbContext を使用することは完全に受け入れられます

したがって、最後に、DbContext の有効期間がこれら 2 つのパラメーターに制限されていることが判明しました。UnitOfWorkスレッド

于 2016-12-13T15:10:00.320 に答える
34

ここで実際に質問に答える単一の答えはありません。OP は、シングルトン/アプリケーションごとの DbContext 設計については質問しませんでした。彼は、(Web) リクエストごとの設計と、どのような潜在的な利点が存在するかについて質問しました。

Mehdi は素晴らしいリソースであるため、http://mehdi.me/ambient-dbcontext-in-ef6/ を参照します。

可能なパフォーマンスの向上。

各 DbContext インスタンスは、データベースからロードしたすべてのエンティティの第 1 レベルのキャッシュを維持します。主キーでエンティティをクエリするときは常に、DbContext はデータベースからクエリを実行するようにデフォルト設定する前に、まず最初のレベルのキャッシュからエンティティを取得しようとします。データ クエリ パターンによっては、複数の連続するビジネス トランザクションで同じ DbContext を再利用すると、DbContext の第 1 レベル キャッシュのおかげで、実行されるデータベース クエリが少なくなる場合があります。

遅延読み込みを有効にします。

サービスが (ビュー モデルやその他の種類の DTO を返すのではなく) 永続的なエンティティを返し、それらのエンティティで遅延読み込みを利用したい場合、それらのエンティティが取得された DbContext インスタンスの有効期間は、それを超えて延長する必要があります。商取引の範囲。サービス メソッドが、返す前に使用した DbContext インスタンスを破棄した場合、返されたエンティティのプロパティを遅延読み込みしようとすると失敗します (遅延読み込みを使用することが良い考えであるかどうかは、まったく別の議論であり、ここでは触れません)。ここ)。この Web アプリケーションの例では、遅延読み込みは通常、別のサービス レイヤーによって返されるエンティティのコントローラー アクション メソッドで使用されます。その場合、

デメリットもありますので注意してください。そのリンクには、この件に関して読むべき他の多くのリソースが含まれています。

他の誰かがこの質問に出くわし、実際に質問に対処していない回答に夢中にならない場合に備えて、これを投稿してください。

于 2015-12-15T23:34:49.117 に答える
21

DbContext がまったくスレッドセーフではないためだと確信しています。したがって、物事を共有することは決して良い考えではありません。

于 2012-05-14T15:03:57.183 に答える
17

質問や議論で実際に扱われていないことの 1 つは、DbContext が変更をキャンセルできないという事実です。変更を送信することはできますが、変更ツリーをクリアすることはできません。そのため、リクエストごとのコンテキストを使用する場合、何らかの理由で変更を破棄する必要がある場合は不運です。

個人的には、必要に応じて DbContext のインスタンスを作成します。通常は、必要に応じてコンテキストを再作成できるビジネス コンポーネントにアタッチされます。そうすれば、単一のインスタンスを強制されるのではなく、プロセスを制御できます。また、実際に使用されるかどうかに関係なく、コントローラーの起動ごとに DbContext を作成する必要もありません。次に、リクエストごとのインスタンスが必要な場合は、CTOR で (DI または手動で) 作成するか、各コントローラー メソッドで必要に応じて作成します。個人的には、実際には必要ない場合に DbContext インスタンスを作成しないようにするために、通常は後者のアプローチを取ります。

また、どの角度から見るかにもよります。私にとって、リクエストごとのインスタンスは決して意味がありません。DbContext は本当に Http リクエストに属していますか? 行動に関しては、それは間違った場所です。ビジネス コンポーネントは、HTTP リクエストではなく、コンテキストを作成する必要があります。その後、必要に応じてビジネス コンポーネントを作成または破棄することができ、コンテキストの有効期間について心配する必要はありません。

于 2016-02-25T01:14:45.983 に答える
12

以前の意見に同意します。シングル スレッド アプリで DbContext を共有する場合は、より多くのメモリが必要になります。たとえば、Azure 上の Web アプリケーション (1 つの極小インスタンス) にはさらに 150 MB のメモリが必要で、1 時間あたり約 30 人のユーザーがいます。 HTTP 要求で DBContext を共有するアプリケーション

これが実際のサンプル画像です: アプリケーションは 12PM にデプロイされました

于 2013-04-08T13:36:22.780 に答える
3

シングル スレッドのシングル ユーザー アプリケーションであっても、シングルトン DbContext を使用しないもう 1 つの控えめな理由は、それが使用する ID マップ パターンです。これは、クエリまたは ID を使用してデータを取得するたびに、取得されたエンティティ インスタンスがキャッシュに保持されることを意味します。次に同じエンティティを取得すると、エンティティのキ​​ャッシュされたインスタンスが提供されます (利用可能な場合)。これには、同じセッションで行った変更が含まれます。これは、SaveChanges メソッドが同じデータベース レコードの複数の異なるエンティティ インスタンスにならないようにするために必要です。それ以外の場合、コンテキストはこれらすべてのエンティティ インスタンスからのデータを何らかの方法でマージする必要があります。

問題である理由は、シングルトン DbContext が時限爆弾になり、最終的にデータベース全体 + .NET オブジェクトのオーバーヘッドをメモリにキャッシュする可能性があるためです。

.NoTracking()拡張メソッドで Linq クエリのみを使用することで、この動作を回避する方法があります。また、最近の PC には大量の RAM が搭載されています。しかし、通常、それは望ましい動作ではありません。

于 2015-08-06T18:52:23.850 に答える
3

私が気に入っているのは、(ユーザーが見ているように) 作業単位 (つまり、ページの送信) を ORM の意味での作業単位に合わせることです。

したがって、新しいコンテキストを作成するたびに CRUD メソッドを公開していた場合はできなかった、ページ全体の送信をトランザクション化できます。

于 2012-05-14T14:58:59.767 に答える
1

Entity Framework で特に注意すべきもう 1 つの問題は、新しいエンティティの作成、遅延読み込み、およびそれらの新しいエンティティの使用 (同じコンテキストから) を組み合わせて使用​​する場合です。IDbSet.Create (vs just 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.
于 2016-03-23T17:04:16.490 に答える