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

213

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

Однако некоторые коллеги хотят разбить функциональные области на отдельные DbContextклассы.

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

Поэтому я ищу:

А) конкретные примеры того, почему это может быть плохой идеей;

Б) заверения, что все будет хорошо.

Джош Шульц
источник
Смотрите мой ответ: stackoverflow.com/questions/8244405/…
Мохсен Алихани,

Ответы:

168

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

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

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

Ладислав Мрнка
источник
21
Использование одного контекста для приложения может быть дорогостоящим, если в приложении много сущностей / таблиц. Таким образом, в зависимости от схемы может также иметь смысл иметь несколько контекстов.
DarthVader
7
Поскольку я не подписываюсь на pluralsight, я нашел эту замечательную статью Джули Лерман ( ее комментарий ), написанную задолго до этого вопроса
Дейв Т.
Я полагаю, сущностный каркас для поддержки нескольких dbcontexts в одной базе данных по соглашению об именах. По этой причине я до сих пор пишу свой собственный ORM для модульных приложений. Это трудно поверить, это заставляет одно приложение использовать одну базу данных. Особенно в веб-фермах у вас ограниченное количество баз данных
свободная воля
Кроме того, я понял, что вы можете включить-миграцию только для одного контекста внутри проекта через консоль PM.
Петр Квятек
9
@PiotrKwiatek Не уверен, что это изменилось между вашим комментарием и сейчас, но Enable-Migrations -ContextTypeName MyContext -MigrationsDirectory Migrations\MyContextMigrationsработает сейчас.
Зак
60

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

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

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

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

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

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

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

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

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

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

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

Франсиско д'Анкония
источник
8
Я должен согласиться здесь, особенно когда речь идет о существующей базе данных. Я сейчас работаю над этой проблемой, и мое внутреннее чувство до сих пор таково: 1. Наличие одной и той же физической таблицы в разных контекстах - плохая идея. 2. Если мы не можем решить, что таблица принадлежит тому или иному контексту, то два контекста недостаточно различимы для логического разделения.
Jkerak
3
Я бы сказал, что при выполнении CQRS у вас не будет никаких связей между контекстами (каждое представление может иметь свой собственный контекст), поэтому это предупреждение не относится к каждому случаю, когда может потребоваться несколько контекстов. Вместо объединения и ссылок используйте дублирование данных для каждого контекста. - Это не отрицает полезность этого ответа, хотя :)
urbanhusky
1
Я чувствовал боль, с которой вы столкнулись глубоко! Я тоже считаю, что один контекст - лучший выбор для простоты.
Ахмет
1
Мой единственный аргумент против, отмечая, что в остальном я полностью согласен, касается идентичности. Особенно с горизонтальным масштабированием, уровень идентификации должен быть выделен почти во всех случаях, когда вводится балансировка нагрузки. По крайней мере, это то, что я нахожу.
Барри
5
Мне кажется, что вы не прошли DDD полностью, если ваши агрегаты должны были знать другие агрегаты. Если вам нужно сослаться на что-то, есть две причины: они находятся в одной совокупности, что означает, что они должны быть изменены в одной и той же транзакции или нет, и вы ошиблись в своих границах.
Simons0n
54

Эта тема только что всплыла на StackOverflow, и поэтому я хотел предложить еще одну «Б) гарантию, что все будет хорошо» :)

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

Джули Лерман
источник
7
Учебное видео о множественном освещении было очень хорошим для объяснения больших понятий, однако, имхо, приведенные вами примеры слишком тривиальны по сравнению с корпоративным решением (где, например, существует NuGet сборок с определениями DbContext или динамически загружаются модульные сборки). Ограниченный контекст DDD был полностью нарушен вашим последним примером, где был определен дубликат DbContext для хранения дублированных объявлений для каждого DbSet. Я ценю, что вы ограничены технологией. Мне действительно нравятся твои видео, но это заставило меня хотеть большего.
Виктор Ромео
5
Я определенно стремился к большой картине. Проблемы с пакетами Nuget в Больших приложениях довольно неконтролируемы для видео ef. Ре "сломанные" примеры ... А? Может быть, лучше отнести это к частной беседе, так как критика моего курса выходит за рамки (и, возможно, неуместна) для этого форума. Я думаю, что ТАК позволяет вам связаться со мной напрямую.
Джули Лерман
57
Было бы неплохо, чтобы Джули поделилась информацией о проблеме / вопросе ОП. Вместо этого пост предназначен только для поощрения платной подписки на множественное понимание. Если бы плагин для продукта, была бы полезна хотя бы ссылка на информацию о предлагаемом решении (DDD Bounded context Pattern). Является ли «DDD» тем, что описано на стр. 222 «Среда программирования объекта: DBContext»? Потому что я искал (без индекса) «DDD» или даже «Ограниченный контекст» и не могу найти ... Не могу дождаться, когда вы внесете новые изменения для EF6 ...
Сострадательный
12
извините, я не пытался продвигать, а просто добавлял в ОП "хочу гарантии". Ладислав и другие проделали большую работу с деталями. Так что я просто пытался сделать что-то, что я уже создал, и которое углубляется в гораздо больше, чем я мог бы передать на SO. Вот другие ресурсы, где я подробно рассказал о некоторых вещах: msdn.microsoft.com/en-us/magazine/jj883952.aspx & msdn.microsoft.com/en-us/magazine/dn342868.aspx & oredev.org / 2013 / wed-fri-conference /…
Джули Лерман
краткое изложение предложений кода @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»). Что еще более важно это также префикс __MigrationHistoryтаблицы (таблиц), например Customer.__MigrationHistory. Таким образом, вы можете иметь более одной __MigrationHistoryтаблицы в одной базе данных, по одной для каждого контекста. Таким образом, изменения, которые вы делаете для одного контекста, не будут мешать другому.

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


Резюме

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

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

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

Мартин
источник
Что вы делаете, когда вам нужно обновить 2 сущности, которые находятся в разных контекстах?
августа
Я хотел бы создать новый (сервисный) класс, который знает оба контекста, придумать хорошее имя и обязанности этого класса и выполнить это обновление одним из его методов.
Мартин
7

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

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

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

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

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

Choco
источник
2
Это очень помогло. «Вторичный» контекст не должен объявлять общую таблицу. Просто добавьте это DbSet<x>определение вручную . Я делаю это в частичном классе, соответствующем тому, что делает EF Designer.
Глен Литтл
Вы избавили меня от головной боли, сэр! Вы предоставили конкретное решение вместо принятого ответа. Очень ценится!
WoIIe
6

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

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

Илан
источник
6
Вместо того, чтобы вырезать и вставить, не могли бы вы просто позвонить OtherContext.OnModelCreating()из вашего комбинированного контекста?
AlexFoxGill
4

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

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

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

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

Виктор Дж. Гарсия
источник
3
Я работал над большой системой, где у нас было несколько контекстов. Одна из вещей, которые я обнаружил, заключалась в том, что иногда вам приходилось включать один и тот же DbSet в несколько контекстов. С одной стороны, это устраняет некоторые проблемы с чистотой, но позволяет завершить ваши запросы. В случае, когда есть определенные административные таблицы, которые вам нужно прочитать, вы можете добавить их в базовый класс DbContext и наследовать их в контексте вашего модуля приложения. Ваша «реальная» цель контекста администратора может быть переопределена как «обеспечение поддержки таблиц администратора», а не предоставление полного доступа к ним.
JMarsch
1
Что бы это ни стоило, я всегда ходил взад и вперед по поводу того, стоило ли оно того. С одной стороны, с отдельным контекстом, есть меньше информации для разработчика, который просто хочет работать над одним модулем, и вы чувствуете себя более безопасным при определении и использовании пользовательских проекций (потому что вас не волнует влияние, которое это окажет на другие модули). С другой стороны, вы сталкиваетесь с некоторыми проблемами, когда вам нужно обмениваться данными в контексте.
JMarsch
1
Вы не ДОЛЖНЫ включать сущности в оба, вы всегда можете получить идентификаторы и сделать второй запрос в другом контексте. Для небольших систем это плохо, для больших БД / систем с большим количеством разработчиков согласованность многостоловых структур является гораздо большей и более сложной проблемой, чем 2 запроса.
user1496062
4

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

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

OzBob
источник
2

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

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

    }
    public DbSet<Movie> Movies { get; set; }
}
Даниил
источник
Да, вы можете, но как вы можете делать запросы из разных сущностей в разных контекстах БД?
Реза
2

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

Мигель Дельгадо
источник
1

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

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

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

freilebt
источник
2
Я не вижу ни одного вопроса в своем ответе. Я не задаю один единственный вопрос! Скорее поделитесь сценарием, в котором тема обсуждения имеет смысл.
Freilebt
0

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

Недавно я начал работать над проектом, в котором была одна база данных с 3-мя схемами (первый подход к БД), одна из которых предназначена для управления пользователями. Существовал контекст БД из каждой отдельной схемы. Конечно, пользователи были связаны и с другими схемами, например. схема KB имела таблицу Topic, в которой были «созданы», «последние изменены» и т. д. FK для идентификатора схемы, таблица appuser.

Эти объекты были загружены отдельно в C #, во-первых, тема была загружена из 1 контекста, затем пользователи были загружены через идентификаторы пользователей из другого контекста БД - не приятно, это нужно исправить! (аналогично использованию нескольких dbcontexts в одной базе данных с EF 6 )

Сначала я попытался добавить недостающие инструкции FK из схемы идентификации в схему KB, в EF modelBuilder в контексте базы данных KB. Так же, как если бы был только 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 - Используйте атрибуты для настройки модели (где это возможно). Если опущено, используется только свободный API. С этим указанным параметром свойства объекта были определены не в контексте БД OnModelCreating(), а скорее в самом объекте с атрибутами.

Таким образом, EF получил достаточно информации для генерации правильного оператора SQL с правильными именами полей и схемами.

TL; DR: отдельные контексты БД плохо обрабатывают отношения (FK) между ними, каждый контекст имеет информацию только о своих собственных сущностях. При указании включения «-data-annotations» dotnet ef dbcontext scaffoldэта информация хранится не в каждом отдельном контексте, а в самих объектах БД.

берсерк
источник