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


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

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

11 167

11 ответов:

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

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

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

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

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

разбиение контекстов путем установки schmema по умолчанию

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

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

        // Fluent API configuration
    }   
}

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


резюме

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

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

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

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

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

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

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

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

мы также попытались запросить оба контекста, чтобы получить необходимые нам данные. Например, наша бизнес-логика будет запрашивать половину того, что ей нужно из контекста 1, а другую половину-из контекста 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().

Я просто потратил время на поиски, почему мои отношения каскадного удаления не были сохранены только для того, чтобы обнаружить, что я не портировал 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 и в то же время разделение дает понять программистам, что "наша собственная пользовательская информация" входит в конкретную пользовательская схема, которую мы определили.