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

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

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

11 ответов


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

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

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


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

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


Distiguishing контексты, установив по умолчанию schmema

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

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

        // Fluent API configuration
    }   
}

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

при добавлении миграции укажите полное имя класса конфигурации (производное от 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";
    }
}


резюме

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

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

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


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

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

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

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

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

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

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

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

меня интересуют ваши мысли.


простой пример для достижения указанных ниже:

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

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

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

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


напоминание: Если вы объединяете несколько контекстов, убедитесь, что вы вырезали N вставить все функции в различных RealContexts.OnModelCreating() в одном CombinedContext.OnModelCreating().

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


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

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

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

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


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

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

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

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


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

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


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

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

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