Entity Framework: одна база данных, несколько DbContexts. Это плохая идея?

Мое впечатление на сегодняшний день состояло в том, что DbContext предназначен для представления вашей базы данных, и, таким образом, если ваше приложение использует одну базу данных, вам нужен только один DbContext. Однако некоторые коллеги хотят разбить функциональные области на отдельные классы DbContext. Я считаю, что это происходит из хорошего места - желание сохранить код чище, но он кажется неустойчивым. Моя кишка говорит мне, что это плохая идея, но, к сожалению, мое чувство кишки не является достаточным условием для конструктивного решения.

Итак, я ищу A) конкретные примеры того, почему это может быть плохой идеей, или B) заверения в том, что все это будет хорошо.

Ответ 1

У вас может быть несколько контекстов для единой базы данных. Это может быть полезно, например, если ваша база данных содержит несколько схем базы данных и вы хотите обрабатывать каждую из них как отдельную автономную область.

Проблема в том, когда вы хотите сначала использовать код для создания своей базы данных - только один контекст в вашем приложении может это сделать. Трюк для этого обычно является дополнительным контекстом, содержащим все ваши сущности, которые используются только для создания базы данных. В ваших реальных контекстах приложения, содержащих только подмножества ваших объектов, должен быть установлен инициализатор базы данных на нуль.

Существуют и другие проблемы, которые вы увидите при использовании нескольких типов контекста - например, общие типы сущностей и их переход из одного контекста в другой и т.д. Как правило, это возможно, это может сделать ваш дизайн более чистым и разделить разные функциональные области, но он имеет свои затраты в дополнительной сложности.

Ответ 2

Этот поток просто пузырился на StackOverflow, поэтому я хотел предложить еще одну "B" уверенность, что все будет хорошо ":)

Я делаю именно это с помощью шаблона DDD Bounded Context. Я написал об этом в своей книге "Программирование Entity Framework: DbContext" и в центре 50-минутного модуля на одном из моих курсов по Pluralsight → http://pluralsight.com/training/Courses/TableOfContents/efarchitecture

Ответ 3

Я написал этот ответ около четырех лет назад, и мое мнение не изменилось. Но с тех пор на фронте микросервисов произошли значительные события. В конце я добавил заметки для микро-сервисов...

Я буду взвешивать эту идею, имея реальный опыт, чтобы поддержать мой голос.

Меня привлекло большое приложение, в котором было пять контекстов для одной базы данных. В конце концов, мы удалили все контексты, кроме одного - возвращаясь к одному контексту.

Сначала идея множественных контекстов кажется хорошей идеей. Мы можем разделить наш доступ к данным на домены и предоставить несколько простых и понятных контекстов. Похоже, DDD, верно? Это упростило бы наш доступ к данным. Другой аргумент в пользу производительности - доступ к контексту, который нам нужен.

Но на практике, по мере роста нашего приложения, многие из наших таблиц имели общие связи в разных контекстах. Например, запросы к таблице A в контексте 1 также требовали присоединения к таблице B в контексте 2.

Это оставило нам пару неудачных вариантов. Мы могли бы дублировать таблицы в различных контекстах. Мы попробовали это. Это создало несколько проблем отображения, включая ограничение EF, которое требует, чтобы у каждого объекта было уникальное имя. Таким образом, мы получили сущности с именами Person1 и Person2 в разных контекстах. Можно утверждать, что это был плохой дизайн с нашей стороны, но, несмотря на все наши усилия, наше приложение действительно росло в реальном мире.

Мы также попытались запросить оба контекста, чтобы получить нужные нам данные. Например, наша бизнес-логика будет запрашивать половину того, что ей нужно, из контекста 1, а другую половину - из контекста 2. У этого были некоторые серьезные проблемы. Вместо того чтобы выполнять один запрос к одному контексту, нам приходилось выполнять несколько запросов в разных контекстах. Это реальное снижение производительности.

В конце концов, хорошая новость заключается в том, что было легко исключить несколько контекстов. Контекст должен быть легким объектом. Поэтому я не думаю, что производительность - хороший аргумент для нескольких контекстов. Я полагаю, что почти во всех случаях отдельный контекст проще, менее сложен и, вероятно, будет работать лучше, и вам не придется реализовывать несколько обходных путей, чтобы заставить его работать.

Я подумал об одной ситуации, когда несколько контекстов могут быть полезны. Отдельный контекст можно использовать для устранения физической проблемы с базой данных, в которой она фактически содержит более одного домена. В идеале контекст должен быть один к одному для домена, который будет один к одному для базы данных. Другими словами, если набор таблиц никоим образом не связан с другими таблицами в данной базе данных, их, вероятно, следует извлечь в отдельную базу данных. Я понимаю, что это не всегда практично. Но если набор таблиц настолько отличается, что вам будет удобно разделить их на отдельную базу данных (но вы не захотите), тогда я мог бы рассмотреть возможность использования отдельного контекста, но только потому, что на самом деле есть два отдельных домена.

Что касается микросервисов, то единый контекст все еще имеет смысл. Однако для микросервисов каждый сервис будет иметь свой собственный контекст, который включает только таблицы базы данных, относящиеся к этому сервису. Другими словами, если служба x обращается к таблицам 1 и 2, а служба y обращается к таблицам 3 и 4, каждая служба будет иметь свой собственный уникальный контекст, который включает в себя таблицы, специфичные для этой службы.

Мне интересны ваши мысли.

Ответ 4

Различение контекстов путем установки схемы по умолчанию

В EF6 вы можете иметь несколько контекстов, просто укажите имя схемы базы данных по умолчанию в OnModelCreating методе из вас DbContext производного класса (где конфигурация Fluent-АФИ). Это будет работать в EF6:

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

        // Fluent API configuration
    }   
}

В этом примере будет использоваться "Клиент" в качестве префикса для таблиц вашей базы данных (вместо "dbo"). Еще важнее то, что он также будет __MigrationHistory префикс таблиц __MigrationHistory, например, Customer.__MigrationHistory. Таким образом, вы можете иметь более одной таблицы __MigrationHistory в одной базе данных, по одной для каждого контекста. Таким образом, изменения, которые вы делаете для одного контекста, не будут мешать другому.

При добавлении миграции укажите полное имя вашего класса конфигурации (полученного из DbMigrationsConfiguration) в качестве параметра в команде add-migration DbMigrationsConfiguration:

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";
    }
}


Резюме

В общем, вы можете сказать, что все четко разделено: контексты, папки миграции в проекте и таблицы в базе данных.

Я бы выбрал такое решение, если есть группы сущностей, которые являются частью большой темы, но не связаны (через внешние ключи) друг с другом.

Если группы сущностей не имеют ничего общего друг с другом, я бы создал отдельную базу данных для каждого из них, а также получил бы доступ к ним в разных проектах, возможно, с одним единственным контекстом в каждом проекте.

Ответ 5

Напоминание: если вы комбинируете несколько контекстов, убедитесь, что вы разрезаете n вставляете все функциональные возможности вашего различного RealContexts.OnModelCreating() в свой одиночный CombinedContext.OnModelCreating().

Я просто потратил время на то, чтобы выяснить, почему мои связи с каскадным удалением не сохраняются, только чтобы обнаружить, что я не перенес код modelBuilder.Entity<T>()....WillCascadeOnDelete(); из моего реального контекста в мой комбинированный контекст.

Ответ 6

Простой пример для достижения ниже:

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

Просто укажите свойства в основном контексте: (используется для создания и поддержки БД) Примечание. Просто используйте protected: (Entity здесь не отображается)

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: Выделение отдельной Entity здесь

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, а затем создать дополнительные контексты, необходимые для каждого разделения схем.

Все они используют одну и ту же строку соединения, однако используют отдельные соединения, поэтому не перекрещиваются транзакции и не знают о проблемах с блокировкой. Как правило, вы планируете разделение, чтобы этого не произошло.

Ответ 7

Моя кишка сказала мне то же самое, когда я столкнулся с этим дизайном.

Я работаю над кодовой базой, в которой есть три dbContexts для одной базы данных. 2 из 3 dbcontexts зависят от информации из 1 dbcontext, потому что она обслуживает административные данные. Эта конструкция ограничивает возможности запроса данных. Я столкнулся с этой проблемой, когда вы не можете присоединиться к dbcontexts. Вместо этого вы должны выполнить запрос двух отдельных dbcontexts, затем выполнить объединение в память или выполнить итерацию обоих, чтобы получить комбинацию из двух в качестве результата. Проблема заключается в том, что вместо запроса для определенного набора результатов вы теперь загружаете все свои записи в память, а затем выполняете объединение против двух наборов результатов в памяти. Это может действительно замедлить работу.

Я бы спросил вопрос " только потому, что вы можете, если бы вы?"

См. Эту статью для проблемы, с которой я столкнулся, связанной с этим дизайном. Указанное выражение LINQ содержит ссылки на запросы, связанные с различными контекстами

Ответ 8

В первом коде вы можете иметь несколько DBContext и только одну базу данных. Вам просто нужно указать строку соединения в конструкторе.

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

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

Ответ 9

Еще одна "мудрость". У меня есть база данных, обращенная к обоим, к интернету и к внутреннему приложению. У меня есть контекст для каждого лица. Это помогает мне сохранить дисциплинированную, обеспеченную сегрегацию.

Ответ 10

Вдохновленный [@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")
  {}
}   

"Если вы делаете новую разработку и хотите, чтобы Code First создавал или переносил вашу базу данных на основе ваших классов, вам нужно создать" uber-model ", используя DbContext, который включает в себя все классы и отношения, необходимые для сборки полную модель, которая представляет базу данных. Однако этот контекст не должен наследовать от BaseContext". JL

Ответ 11

Я хочу поделиться случаем, когда я думаю, что возможность иметь несколько DBContexts в одной базе данных имеет смысл.

У меня есть решение с двумя базами данных. Один для данных домена, кроме информации о пользователе. Другой предназначен исключительно для информации пользователя. Это разделение главным образом обусловлено Общим регламентом ЕС о защите данных. Имея две базы данных, я могу свободно перемещать данные домена (например, из Azure в мою среду разработки), пока пользовательские данные остаются в одном безопасном месте.

Теперь для пользовательской базы данных я реализовал две схемы через EF. Один из них по умолчанию предоставляется платформой AspNet Identity. Другой - наша собственная реализация чего-либо еще, связанного с пользователем. Я предпочитаю это решение расширению схемы ApsNet, потому что я могу легко обрабатывать будущие изменения в AspNet Identity, и в то же время разделение дает понять программистам, что "наша собственная пользовательская информация" идет в определенной пользовательской схеме, которую мы определили,