エンティティフレームワーク:1つのデータベース、複数のDbContexts。これは悪い考えですか?[閉まっている]


212

これまでの私の印象DbContextは、はデータベースを表すことを目的としているため、アプリケーションが1つのデータベースを使用する場合は、1つだけが必要だと考えていましたDbContext

ただし、一部の同僚は、機能領域を個別のDbContextクラスに分割したいと考えています。

これは良い場所から来ていると思います-コードをよりクリーンに保ちたいという願望-が不安定なようです。私の腸はそれが悪い考えだと私に言っていますが、残念ながら、私の腸の感覚は設計決定のための十分な条件ではありません。

だから私は探しています:

A)これが悪い考えである理由の具体例;

B)これがすべてうまくいくという保証。


:私の答えを参照してくださいstackoverflow.com/questions/8244405/...
モフセンAlikhani

回答:


168

単一のデータベースに対して複数のコンテキストを持つことができます。たとえば、データベースに複数のデータベーススキーマが含まれていて、それぞれを独立した独立した領域として処理する場合に便利です。

問題は、最初にコードを使用してデータベースを作成する場合です。アプリケーションで実行できるのは単一のコンテキストのみです。このトリックは通常、すべてのエンティティを含む1つの追加コンテキストで、データベースの作成にのみ使用されます。エンティティのサブセットのみを含む実際のアプリケーションコンテキストでは、データベース初期化子をnullに設定する必要があります。

複数のコンテキストタイプを使用すると、他の問題が発生します。たとえば、共有エンティティタイプや、あるコンテキストから別のコンテキストへの受け渡しなどです。一般に、デザインをよりクリーンにして、異なる機能領域を分離できますが、さらに複雑なコスト。


21
アプリケーションに多数のエンティティ/テーブルがある場合、アプリごとに単一のコンテキストを使用するとコストが高くなる可能性があります。したがって、スキーマによっては、複数のコンテキストを持つことも意味があります。
DarthVader 2013年

7
私は複数のサイトを購読していないので、このQ / Aの後に書かれたジュリーラーマン(彼女のコメント)によるこの素晴らしい記事を見つけましたが、非常に適切です:msdn.microsoft.com/en-us/magazine/jj883952.aspx
Dave T.

エンティティフレームワークは、命名規則によって同じデータベースで複数のdbcontextをサポートすることをお勧めします。このため、モジュラーアプリケーションを目的とした独自のORMを作成しています。それは信じがたいことですが、単一のアプリケーションが単一のデータベースを使用することを強制します。特にWebファームでは、データベースの数が限られています
freewill

さらに、PMコンソールを介してプロジェクト内の1つのコンテキストに対してのみ移行を有効にできることに気付きました。
Piotr Kwiatek 2014年

9
@PiotrKwiatekこれがコメントと現在の間で変わったかどうかはわかりませんが、現在はEnable-Migrations -ContextTypeName MyContext -MigrationsDirectory Migrations\MyContextMigrations機能します。
Zack

60

私は4年ほど前にこの回答を書きましたが、私の意見は変わっていません。しかし、それ以来、マイクロサービスの分野で大きな進展がありました。最後にマイクロサービス固有のメモを追加しました...

投票を裏付けるための実際の経験を踏まえて、このアイデアと比較検討します。

単一のデータベースに対して5つのコンテキストを持つ大規模なアプリケーションが導入されました。最後に、1つを除いてすべてのコンテキストを削除し、単一のコンテキストに戻しました。

最初は、複数のコンテキストのアイデアは良いアイデアのように思えます。データアクセスをドメインに分離し、クリーンで軽量なコンテキストをいくつか提供できます。DDDのように聞こえますか?これにより、データアクセスが簡素化されます。もう1つの議論は、必要なコンテキストにのみアクセスするという点でのパフォーマンスです。

しかし実際には、アプリケーションが成長するにつれて、テーブルの多くはさまざまなコンテキスト間で関係を共有していました。たとえば、コンテキスト1のテーブルAへのクエリには、コンテキスト2のテーブルBの結合も必要でした。

これは私たちにいくつかの悪い選択を残しました。さまざまなコンテキストでテーブルを複製できます。これを試しました。これにより、各エンティティに一意の名前を付ける必要があるEF制約を含む、いくつかのマッピングの問題が発生しました。したがって、異なるコンテキストでPerson1とPerson2という名前のエンティティが作成されました。これは私たちにとっては貧弱な設計であると主張することもできますが、最善の努力にもかかわらず、これが私たちのアプリケーションが実際に現実の世界で成長した方法です。

また、必要なデータを取得するために両方のコンテキストをクエリしてみました。たとえば、ビジネスロジックは、必要なものの半分をコンテキスト1からクエリし、残りの半分をコンテキスト2からクエリします。これにはいくつかの大きな問題がありました。単一のコンテキストに対して1つのクエリを実行する代わりに、異なるコンテキスト全体で複数のクエリを実行する必要がありました。これには実際のパフォーマンスの低下があります。

結局、良いニュースは、複数のコンテキストを簡単に取り除くことができたことです。コンテキストは軽量オブジェクトを意図しています。ですから、パフォーマンスは複数のコンテキストにとって良い議論ではないと思います。ほとんどすべての場合、単一のコンテキストの方が単純で複雑ではなく、パフォーマンスが向上する可能性が高いと思います。これを機能させるために一連の回避策を実装する必要はありません。

複数のコンテキストが役立つ1つの状況について考えました。別のコンテキストを使用して、実際に複数のドメインが含まれているデータベースの物理的な問題を修正できます。理想的には、コンテキストはドメインと1対1であり、ドメインはデータベースと1対1です。言い換えると、テーブルのセットが特定のデータベース内の他のテーブルとまったく関係がない場合、それらはおそらく別のデータベースに引き出される必要があります。これが常に実用的であるとは限りません。しかし、テーブルのセットが非常に異なっているため、それらを別のデータベースに分離しても問題がない場合(そうしないことを選択した場合)、実際には2つの別個のドメインがあるという理由だけで、別個のコンテキストを使用するケースが見られます。

マイクロサービスに関しては、単一のコンテキストが依然として理にかなっています。ただし、マイクロサービスの場合、各サービスには、そのサービスに関連するデータベーステーブルのみを含む独自のコンテキストがあります。言い換えると、サービスxがテーブル1と2にアクセスし、サービスyがテーブル3と4にアクセスする場合、各サービスには、そのサービスに固有のテーブルを含む独自のコンテキストがあります。

あなたの考えに興味があります。


8
特に既存のデータベースを対象とする場合は、ここで同意する必要があります。私は今この問題に取り組んでおり、今のところ私の直感は次のとおりです。1.複数のコンテキストで同じ物理テーブルを持つことは悪い考えです。2.テーブルがいずれかのコンテキストに属していると判断できない場合、2つのコンテキストは論理的に分離できるほど明確ではありません。
jkerak 2016年

3
CQRSを実行する場合、コンテキスト間に関係がないため(各ビューに独自のコンテキストがある可能性があります)、この警告は、複数のコンテキストが必要になるすべてのケースに適用されるわけではないと主張します。結合して参照する代わりに、各コンテキストにデータ複製を使用します。-しかし、これはこの回答の有用性を否定するものではありません:)
urbanhusky

1
あなたが直面した痛みを深く感じました!:/また、単純にするために、1つのコンテキストの方が適していると思います。
2018

1
それ以外の点では完全に同意するということに反対する私の唯一の反対は、アイデンティティに関するものです。特に水平スケーリングでは、ロードバランシングが導入されるほとんどすべてのケースでIDレイヤーを分離する必要があります。少なくとも、それは私が見つけているものです。
バリー

5
私には、アグリゲートが他のアグリゲートを知る必要がある場合、DDDを最後まで行っていなかったように見えます。何かを参照する必要がある場合、2つの理由があります。それらが同じ集計内にあることは、同じトランザクションで変更する必要があるか、変更しない必要があるか、境界が間違っていることを意味します。
Simons0n 2018年

54

このスレッドはStackOverflowでバブルアップしたばかりなので、別の「B)これですべて問題ないことを保証したいと思いました:)

これは、DDD Bounded Contextパターンを使用して正確に行っています。それについては私の著書 『Programming Entity Framework:DbContext』で書いており、Pluralsight-> http://pluralsight.com/training/Courses/TableOfContents/efarchitectureのコースの1つにある50分のモジュールの焦点です。


7
複数視力トレーニングビデオは大きな概念を説明するのに非常に優れていましたが、私が提供する例は、エンタープライズソリューションに比べて取るに足りないものです(たとえば、DbContext定義を含むアセンブリのNuGetが存在する、またはモジュールアセンブリが動的にロードされる)。 DDD Bounded Contextは、各DbSetへの重複した宣言を保持するために重複したDbContextが定義された最後の例によって完全に壊れました。私はあなたが技術によって制限されていることを感謝します。私は本当にあなたのビデオが好きですが、これは私がもっと欲しがっていました。
ビクターロメオ

5
全体像を狙ってデフでした。Bigアプリのnugetパッケージの問題は、efビデオのコンテキストからかなり外れています。「壊れた」例をもう一度...え?私のコースの批評はこのフォーラムにとってかなり範囲外(そしておそらく不適切)なので、これを私的な会議に持っていく方が良いでしょう。SOから直接ご連絡させていただきます。
Julie Lerman、2012年

57
ジュリーにOPの問題/質問に関する情報を共有してもらうと良かったでしょう。代わりに、投稿は複数の洞察に対する有料サブスクリプションを宣伝するだけです。製品のプラグインの場合、少なくとも、推奨されるソリューション(DDD境界コンテキストパターン)に関する情報へのリンクが役立つでしょう。「DDD」は「Programming Entity Framework:DBContext」のp.222に記載されているものですか?'DDD'または 'Bounded Context'を(インデックスなしで)探し、見つからないため... EF6の新しいリビジョンを作成するのを待つことができません...
思いやりのあるナルシスト

12
申し訳ありませんが、私は昇進しようとしておらず、OPに「保証が欲しい」だけを追加していました。ラディスラフと他の人々は、細部にわたって素晴らしい仕事をしました。だから私は、自分で作成した何かを試してみただけで、SOで中継できるよりもはるかに深いものになっています。ここで私はスタッフ徹底的のいくつかを紹介してきた他のリソースは、次のとおりです。msdn.microsoft.com/en-us/magazine/jj883952.aspxmsdn.microsoft.com/en-us/magazine/dn342868.aspxoredev.org / 2013 / wed-fri-conference /…
Julie Lerman

@JulieLermanのコード提案の概要は私の答えを参照してくださいstackoverflow.com/a/36789520/1586498
OzBob

46

デフォルトのスキーマを設定してコンテキストを区別する

EF6では、複数のコンテキストを持つことができ、派生クラスのOnModelCreatingメソッドDbContext(Fluent-API構成がある場所)でデフォルトのデータベーススキーマの名前を指定するだけです。これはEF6で機能します。

public partial class CustomerModel : DbContext
{   
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.HasDefaultSchema("Customer");

        // Fluent API configuration
    }   
}

この例では、データベーステーブルのプレフィックスとして( "dbo"ではなく) "Customer"を使用します。さらに重要なことには、__MigrationHistoryテーブルの前にプレフィックスが付けられますCustomer.__MigrationHistory。したがって__MigrationHistory、コンテキストごとに1つずつ、1つのデータベースに複数のテーブルを含めることができます。したがって、1つのコンテキストに対して行った変更は、他のコンテキストを台無しにすることはありません。

移行を追加するときは、構成クラスの完全修飾名(から派生DbMigrationsConfiguration)をadd-migrationコマンドのパラメーターとして指定します。

add-migration NAME_OF_MIGRATION -ConfigurationTypeName FULLY_QUALIFIED_NAME_OF_CONFIGURATION_CLASS


コンテキストキーの短い言葉

このMSDNの記事によると「章-同じデータベースを対象に、複数のモデルが」EF 6は、おそらく唯一の場合でも、状況を処理するだろうMigrationHistory、テーブルに存在するため、テーブルには、存在していたContextKeyの移行を区別するための列が。

ただしMigrationHistory、上記で説明したようにデフォルトのスキーマを指定して、複数のテーブルを用意することをお勧めします。


別の移行フォルダを使用する

このようなシナリオでは、プロジェクト内のさまざまな「移行」フォルダーを操作することもできます。プロパティDbMigrationsConfigurationを使用して、それに応じて派生クラスを設定できますMigrationsDirectory

internal sealed class ConfigurationA : DbMigrationsConfiguration<ModelA>
{
    public ConfigurationA()
    {
        AutomaticMigrationsEnabled = false;
        MigrationsDirectory = @"Migrations\ModelA";
    }
}

internal sealed class ConfigurationB : DbMigrationsConfiguration<ModelB>
{
    public ConfigurationB()
    {
        AutomaticMigrationsEnabled = false;
        MigrationsDirectory = @"Migrations\ModelB";
    }
}


概要

全体として、すべてが明確に分離されていると言えます:コンテキスト、プロジェクトのMigrationフォルダー、データベースのテーブル。

より大きなトピックの一部であるが、外部キーを介して互いに関連していないエンティティのグループがある場合、私はそのようなソリューションを選択します。

エンティティのグループが互いに何もする必要がない場合は、エンティティごとに個別のデータベースを作成し、さまざまなプロジェクトでそれらにアクセスします。おそらく、各プロジェクトで1つのコンテキストを使用します。


異なるコンテキストにある2つのエンティティを更新する必要がある場合はどうしますか?
18年

私は両方のコンテキストを知っている新しい(サービス)クラスを作成し、このクラスの適切な名前と責任を考え、そのメソッドの1つでこの更新を行います。
マーティン、

7

以下を実現する簡単な例:

    ApplicationDbContext forumDB = new ApplicationDbContext();
    MonitorDbContext monitor = new MonitorDbContext();

メインコンテキストでプロパティをスコープするだけです:(DBの作成と保守に使用されます)注:保護されたものを使用してください:(エンティティはここでは公開されていません)

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext()
        : base("QAForum", throwIfV1Schema: false)
    {

    }
    protected DbSet<Diagnostic> Diagnostics { get; set; }
    public DbSet<Forum> Forums { get; set; }
    public DbSet<Post> Posts { get; set; }
    public DbSet<Thread> Threads { get; set; }
    public static ApplicationDbContext Create()
    {
        return new ApplicationDbContext();
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
    }
}

MonitorContext:ここで個別のエンティティを公開

public class MonitorDbContext: DbContext
{
    public MonitorDbContext()
        : base("QAForum")
    {

    }
    public DbSet<Diagnostic> Diagnostics { get; set; }
    // add more here
}

診断モデル:

public class Diagnostic
{
    [Key]
    public Guid DiagnosticID { get; set; }
    public string ApplicationName { get; set; }
    public DateTime DiagnosticTime { get; set; }
    public string Data { get; set; }
}

メインのApplicationDbContext内ですべてのエンティティを保護対象としてマークしたい場合は、スキーマの分離ごとに必要に応じて追加のコンテキストを作成します。

それらはすべて同じ接続文字列を使用しますが、別々の接続を使用するため、トランザクションをクロスさせたり、ロックの問題に注意したりしないでください。一般に、分離を設計するので、これはとにかく起こらないはずです。


2
これは大いに役立ちました。「セカンダリ」コンテキストは、共有テーブルを宣言する必要はありません。手動でDbSet<x>定義を追加するだけです。私は、EF Designerが作成したものと一致する部分クラスでそれを行います。
Glen Little

あなたは私に頭痛の種をたくさん救ってくれました!受け入れられた答えの代わりに具体的な解決策を提供しました。本当に感謝!
WoIIe

6

注意:複数のコンテキストを組み合わせる場合は、さまざまな機能のすべての機能を切り取って貼り付けてください。 RealContexts.OnModelCreating()単一のにてくださいCombinedContext.OnModelCreating()

modelBuilder.Entity<T>()....WillCascadeOnDelete();実際のコンテキストから結合されたコンテキストにコードを移植していないことを発見するためだけにカスケード削除関係が保持されなかった理由を探すのに時間を無駄にしました。


6
切り取って貼り付けるのではなくOtherContext.OnModelCreating()、組み合わせたコンテキストから呼び出すだけでいいですか?
AlexFoxGill 2015

4

このデザインに出くわしたとき、私の腸は同じことを私に教えてくれました。

1つのデータベースに3つのdbContextsがあるコードベースで作業しています。3つのdbcontextのうち2つは、管理データを提供するため、1つのdbcontextからの情報に依存しています。この設計では、データのクエリ方法に制約を設けています。dbcontextsを越えて参加できないこの問題に遭遇しました。代わりに、2つの個別のdbcontextにクエリを実行してから、メモリで結合を実行するか、両方を反復して、2つの組み合わせを結果セットとして取得します。これの問題は、特定の結果セットを照会する代わりに、すべてのレコードをメモリにロードしてから、メモリ内の2つの結果セットに対して結合を行うことです。それは本当に物事を遅くすることができます。

できるからといって?"

このデザインに関連して私が遭遇した問題については、この記事を参照してください。 指定されたLINQ式には、さまざまなコンテキストに関連付けられているクエリへの参照が含まれています


3
私は複数のコンテキストがある大規模なシステムで作業しました。私が見つけたものの1つは、複数のコンテキストに同じDbSetを含める必要がある場合があることです。一方で、これはいくつかの純粋性の懸念を解消しますが、クエリを完了することができます。読み取る必要がある特定の管理テーブルがある場合は、それらを基本DbContextクラスに追加して、アプリモジュールコンテキストで継承できます。「実際の」管理コンテキストの目的は、管理コンテキストへのすべてのアクセスを提供するのではなく、「管理テーブルのメンテナンスを提供する」として再定義される可能性があります。
JMarsch 2013年

1
それが価値があるかどうかについて、私は常にそれが価値があるかどうかを行き来しました。一方では、個別のコンテキストを使用すると、1つのモジュールで作業したい開発者が知る必要が少なくなり、カスタムプロジェクションを定義して使用する方が安全だと感じます(他のモジュールへの影響について心配していないため)モジュール)。もう1つは、データをクロスコンテキストで共有する必要があるときに、いくつかの問題に直面することです。
JMarsch 2013年

1
両方にエンティティを含める必要はありません。常にIDを取得し、別のコンテキストに対して2番目のクエリを実行できます。小規模なシステムの場合、これは悪いことです。複数のテーブル構造の開発者のコ​​ヒーレンスが多数ある大規模なDB /システムの場合、2つのクエリよりもはるかに大きく困難な問題です。
user1496062 14年

4

[@JulieLermanのDDD MSDN Mag Article 2013]に触発されました[1]

    public class ShippingContext : BaseContext<ShippingContext>
{
  public DbSet<Shipment> Shipments { get; set; }
  public DbSet<Shipper> Shippers { get; set; }
  public DbSet<OrderShippingDetail> Order { get; set; } //Orders table
  public DbSet<ItemToBeShipped> ItemsToBeShipped { get; set; }
  protected override void OnModelCreating(DbModelBuilder modelBuilder)
  {
    modelBuilder.Ignore<LineItem>();
    modelBuilder.Ignore<Order>();
    modelBuilder.Configurations.Add(new ShippingAddressMap());
  }
}

public class BaseContext<TContext>
  DbContext where TContext : DbContext
{
  static BaseContext()
  {
    Database.SetInitializer<TContext>(null);
  }
  protected BaseContext() : base("DPSalesDatabase")
  {}
}   

「新しい開発を行っており、コードファーストにクラスに基づいてデータベースを作成または移行させたい場合は、必要なすべてのクラスと関係を含むDbContextを使用して「uberモデル」を作成する必要があります。データベースを表す完全なモデルを構築します。ただし、このコンテキストはBaseContextから継承してはなりません。」JL


2

最初のコードでは、複数のDBContextと1つのデータベースのみを持つことができます。コンストラクタで接続文字列を指定するだけです。

public class MovieDBContext : DbContext
{
    public MovieDBContext()
        : base("DefaultConnection")
    {

    }
    public DbSet<Movie> Movies { get; set; }
}

はい、できますが、さまざまなdbコンテキストのさまざまなエンティティからどのようにクエリを実行できますか?
Reza

2

もう一つの「知恵」。インターネットと内部アプリの両方に対応するデータベースがあります。それぞれの顔にコンテキストがあります。これにより、規律ある安全な隔離を維持できます。


1

同じデータベースに複数のDBContextsが存在する可能性は理にかなっていると思います。

2つのデータベースを持つソリューションがあります。1つは、ユーザー情報以外のドメインデータ用です。もう1つは、ユーザー情報専用です。この部門は、主にEU 一般データ保護規則によって推進されています。2つのデータベースを使用することで、ユーザーデータが1つの安全な場所にある限り、ドメインデータを自由に移動できます(Azureから開発環境など)。

ここで、ユーザーデータベース用に、EFを介して2つのスキーマを実装しました。1つは、AspNet Identityフレームワークによって提供されるデフォルトの1つです。もう1つは、ユーザー関連の実装です。ApsNetスキーマの拡張よりもこのソリューションの方が好きです。AspNetIdentityの将来の変更を簡単に処理できると同時に、分離によってプログラマーに「私たち自身のユーザー情報」が定義した特定のユーザースキーマに入ることが明確になるためです。 。


2
返信に質問がありません。質問は1つだけではありません。むしろ、議論のトピックが理にかなっているシナリオを共有します。
freilebt 2018

0

ええと、DBスキーマごとに個別のDBコンテキストの問題にかなりの時間を費やしました。それが他の誰かの役に立つことを願っています...

私は最近、3つのスキーマを持つ1つのデータベース(DBファーストアプローチ)を持つプロジェクトに取り組み始めました。そのうちの1つはユーザー管理用です。個別のスキーマごとにDBコンテキストの足場がありました。もちろん、ユーザーは他のスキーマにも関連していました。スキーマKBには、「作成者」、「最終変更者」などのテーブルトピックがありました。スキーマを識別するためのFK、テーブルappuser。

これらのオブジェクトはC#で個別に読み込まれました。最初に、トピックは1つのコンテキストから読み込まれ、ユーザーは他のDBコンテキストからユーザーIDを介して読み込まれました。(EF 6で同じデータベースで複数のdbcontext使用するのと同様)

最初に、欠落しているFK命令をIDスキーマからKBスキーマに、KB DBコンテキストのEF modelBuilderに追加しようとしました。コンテキストが1つしかなかった場合と同じですが、2つに分離しました。

modelBuilder.Entity<Topic>(entity =>
{
  entity.HasOne(d => d.Creator)
    .WithMany(p => p.TopicCreator)
    .HasForeignKey(d => d.CreatorId)
    .HasConstraintName("fk_topic_app_users");

kb dbコンテキストにユーザーオブジェクトに関する情報がなかったため、機能しませんでした。postgresがエラーを返しました relation "AppUsers" does not exist。Selectステートメントには、スキーマ、フィールド名などに関する適切な情報がありませんでした。

私はほとんどあきらめましたが、実行中にスイッチ「-d」に気づきましたdotnet ef dbcontext scaffold。-data-annotationsの省略形-属性を使用してモデルを構成します(可能な場合)。省略した場合、Fluent APIのみが使用されます。このスイッチを指定すると、オブジェクトのプロパティがdbコンテキストではなく定義されましたOnModelCreating()ではなく、属性を使用してオブジェクト自体にれます。

このようにして、EFは、適切なフィールド名とスキーマを使用して適切なSQLステートメントを生成するための十分な情報を取得しました。

TL; DR:個別のDBコンテキストは、それらの間の関係(FK)を適切に処理しません。各コンテキストには、それ自体のエンティティに関する情報しかありません。「-data-annotations」スイッチをオンに指定するとdotnet ef dbcontext scaffold、これらの情報は、個別のコンテキストごとに保存されますが、DBオブジェクト自体には保存されません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.