web-dev-qa-db-ja.com

エンティティフレームワーク:1つのデータベース、複数のDbContext。これは悪い考えですか?

これまでの私の印象では、DbContextはデータベースを表すことを目的としているため、アプリケーションが1つのデータベースを使用する場合、DbContextは1つだけ必要です。ただし、一部の同僚は、機能領域を個別のDbContextクラスに分割したいと考えています。これは良い場所、つまりコードをきれいに保ちたいという願望から来ていると思いますが、それは不安定なようです。私の腸はそれが悪い考えだと言っていますが、残念ながら私の腸の感覚は設計上の決定のための十分な条件ではありません。

A)これが悪い考えである理由の具体例、またはB)これがすべてうまくいくという保証を探しています。

182
Josh Schultz

単一のデータベースに対して複数のコンテキストを使用できます。たとえば、データベースに複数のデータベーススキーマが含まれていて、それぞれを個別の自己完結領域として処理する場合に便利です。

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

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

152
Ladislav Mrnka

このスレッドはStackOverflowでバブルアップしたため、別の「B)これがすべて正常であることの保証」を提供したかったのです。

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

48
Julie Lerman

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

投票に賛成するための現実世界の経験を踏まえて、この考えに反論します。

単一のデータベースに対して5つのコンテキストを持つ大規模なアプリケーションを使用しました。最終的に、1つを除くすべてのコンテキストを削除し、単一のコンテキストに戻しました。

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

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

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

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

最後に、良いニュースは、複数のコンテキストを簡単に削除できることです。コンテキストは、軽量オブジェクトを目的としています。したがって、パフォーマンスが複数のコンテキストの良い議論だとは思いません。ほとんどすべての場合、単一のコンテキストの方がシンプルで複雑ではなく、パフォーマンスが向上する可能性が高いと考えています。また、動作させるために多くの回避策を実装する必要はありません。

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

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

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

43

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

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

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

        // Fluent API configuration
    }   
}

この例では、データベーステーブルの接頭辞として「dbo」ではなく「Customer」を使用します。さらに重要なのは、__MigrationHistoryテーブルのプレフィックスも付けることです。 Customer.__MigrationHistory。したがって、1つのデータベースに複数の__MigrationHistoryテーブルを、コンテキストごとに1つ持つことができます。したがって、1つのコンテキストに対して行った変更は、他のコンテキストを混乱させません。

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

add-migration NAME_OF_MIGRATION -ConfigurationTypeName FULLY_QUALIFIED_NAME_OF_CONFIGURATION_CLASS


コンテキストキーの短い単語

このMSDNの記事によると、「 チャプター-同じデータベースをターゲットとする複数のモデル "EF 6は、テーブルにMigrationHistoryテーブルが1つしか存在しない場合でもおそらく処理します。なぜなら、テーブルには 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";
    }
}


概要

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

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

エンティティのグループが互いに何もすることがない場合、私はそれらのそれぞれのために別々のデータベースを作成し、また各プロジェクトでおそらく単一のコンテキストで、異なるプロジェクトでそれらにアクセスします。

42
Martin

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

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

6
Ilan

以下を達成する簡単な例:

    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内ですべてのエンティティを保護対象としてマークし、スキーマの分離ごとに必要に応じて追加のコンテキストを作成できます。

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

6
Choco

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

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

できるからといって、そうすべきですか?」という質問をします。

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

4

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

2
Miguel Delgado

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

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

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

[@JulieLermanのDDD MSDN Mag記事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")
  {}
}   

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

1
OzBob

同じデータベースに複数のDBContextが存在する可能性があると考えるケースを共有したいと思います。

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

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

1
freilebt