Entity Framework: одна база даних, кілька DbContexts. Це погана ідея? [зачинено]


213

На сьогодні моє враження склалося так, що A DbContextпризначений для представлення вашої бази даних, і, отже, якщо ваша програма використовує одну базу даних, вам потрібно лише одну DbContext.

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

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

Тому я шукаю:

А) конкретні приклади, чому це може бути поганою ідеєю;

Б) запевнення, що це все вийде просто чудово.


Дивіться мою відповідь: stackoverflow.com/questions/8244405/…
Mohsen Alikhani

Відповіді:


168

Ви можете мати кілька контекстів для однієї бази даних. Це може бути корисно, наприклад, якщо у вашій базі даних є кілька схем бази даних, і ви хочете обробляти кожну з них як окрему окрему область.

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

Є й інші проблеми, які ви побачите при використанні декількох типів контексту - наприклад, типи спільних об'єктів та їх перехід з одного контексту в інший тощо. Взагалі це можливо, це може зробити ваш дизайн набагато більш чистим і розділити різні функціональні області, але він має свої витрати в додатковій складності.


21
Використання одного контексту на додаток може бути дорогим, якщо у програмі багато сутностей / таблиць. Таким чином, залежно від схеми, можливо, також має сенс мати декілька контекстів.
DarthVader

7
Оскільки я не підписуюся на плюралізм, я знайшов цю дивовижну статтю Джулі Лерман ( її коментар ), написану добре після цього запитання, але дуже підходящу: msdn.microsoft.com/en-us/magazine/jj883952.aspx
Дейв Т.

Я пропоную, структуру сутності для підтримки декількох dbcontexts в одній базі даних шляхом іменування convention. З цієї причини я все ще пишу власну ORM для модульної програми. Це важко повірити, це змушує єдине додаток використовувати єдину базу даних. Тим більше, що у веб-фермах у вас обмежена кількість баз даних
вільне місце

Крім того, я зрозумів, що Ви можете ввімкнути міграцію лише для одного контексту всередині проекту через PM Console.
Piotr Kwiatek

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, тому я хотів запропонувати ще одне "B) впевненість, що це все буде добре" :)

Я роблю саме це за допомогою шаблону контексту DDD Bounded Context. Про це я писав у своїй книзі «Рамка програми програмування: DbContext», і це фокус 50-хвилинного модуля в рамках одного з моїх курсів з питань плюралізму -> http://pluralsight.com/training/Courses/TableOfContents/efarchitecture


7
Навчальне відео із плюралізму дуже добре роз'яснило великі поняття, однак, так, приклади, які ви наводите, є надто банальними порівняно з рішенням підприємства (де, наприклад, існує NuGet збірок з визначеннями DbContext, або модульні збірки динамічно завантажуються.) Контекст, обмежений DDD, був повністю порушений вашим останньою прикладом, коли визначено дублікат DbContext для зберігання дублікатів декларацій до кожного DbSet. Я ціную, що ви обмежені технологією. Мені дуже подобаються ваші відео, але це залишило мене, бажаючи більше.
Віктор Ромео

5
Я мав на меті велику картину. Проблеми з повторними пакунками у великих додатках виходять за рамки контексту для ef-відео. Повторно "зламані" приклади ... А? Можливо, краще взяти це до приватного конвою, оскільки критика мого курсу досить поза сферою (і, можливо, недоцільною) для цього форуму. Я думаю, що ТАК дозволяє вам зв’язатися зі мною безпосередньо.
Джулі Лерман

57
Було б добре, щоб Джулі поділилась деякою інформацією щодо питання / питання ОП. Замість цього повідомлення призначене лише для просування плати за підписку на множинне розуміння. Якщо плагін для продукту, принаймні буде корисним посилання на інформацію про запропоноване рішення (контекстний шаблон, обмежений DDD). Чи є "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 / ср-фт-конференція /…
Джулі Лерман

Резюме пропозицій коди @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, тоді створіть додаткові контексти, необхідні для кожного розділення схем.

Усі вони використовують однакову рядок з'єднання, однак вони використовують окремі з'єднання, тому не перехрещуйте транзакції і не забудьте про проблеми з блокуванням. Як правило, ваш дизайн розлуки, так що це не повинно відбуватися в будь-якому випадку.


2
Це дуже допомогло. "Вторинний" контекст не потребує оголошення загальної таблиці. Просто вручну додайте це DbSet<x>визначення. Я роблю це в частковому класі, що відповідає тому, що робить дизайнер EF.
Глен Малий

Ви врятували мені багато головних болів, сер! Ви запропонували конкретне рішення замість прийнятої відповіді. Дуже вдячний!
WoIIe

6

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

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


6
Замість того, щоб вирізати та вставляти, ви могли просто зателефонувати OtherContext.OnModelCreating()зі свого комбінованого контексту?
AlexFoxGill

4

Моя кишка сказала мені те саме, коли я натрапив на цю конструкцію.

Я працюю над базою коду, де в одній базі даних є три dbContexts. 2 з 3 dbcontexts залежать від інформації 1 dbcontext, оскільки вона обслуговує адміністративні дані. Цей дизайн містить обмеження щодо того, як ви можете запитувати свої дані. Я зіткнувся з цією проблемою, коли ви не можете приєднатися до dbcontexts. Натомість те, що вам потрібно зробити, - це запитувати два окремих dbcontexts, а потім робити об'єднання в пам'яті або ітерацію через обидва, щоб отримати комбінацію двох в результаті набору. Проблема в тому, що замість того, щоб запитувати певний набір результатів, тепер ви завантажуєте всі свої записи в пам'ять, а потім робите з'єднання проти двох наборів результатів у пам'яті. Це дійсно може сповільнити справи.

Я би поставив питання "тільки тому, що ти можеш, чи не так? "

Дивіться цю статтю щодо проблеми, з якою я зіткнувся щодо цієї конструкції. Зазначений вираз LINQ містить посилання на запити, пов'язані з різними контекстами


3
Я працював над великою системою, де у нас було декілька контекстів. Одна з речей, яку я виявив, - це те, що іноді доводилося включати один і той же DbSet в декілька контекстів. З одного боку, це порушує деякі питання чистоти, але це дозволяє вам виконати запити. У випадку, коли є певні таблиці адміністрування, які потрібно прочитати, ви можете додати їх до базового класу DbContext та успадкувати їх у контекстах модуля додатка. Ціль вашого "справжнього" контексту адміністратора може бути переосмислена як "забезпечення технічного обслуговування адміністраторських таблиць", а не надання повного доступу до них.
JMarsch

1
Що я того вартий, я завжди ходила вперед і назад над тим, чи варто воно того. З одного боку, з окремими контекстами можна менше знати про розробника, який просто хоче працювати над одним модулем, і ви відчуваєте себе безпечнішим у визначенні та використанні користувацьких проекцій (тому що ви не турбуєтесь про ефекти, які він матиме на інший модулі). З іншого боку, ви стикаєтесь з деякими проблемами, коли вам потрібно обмінюватися даними між контекстом.
JMarsch

1
Ви не ВДАЄТЕ включати сутності в обидва, ви завжди можете отримати ідентифікатори та зробити другий запит у іншому контексті. Для малих систем це погано, оскільки для великих БД / систем з багатьма розробниками узгодженість структур декількох таблиць є набагато більшою і складнішою проблемою, ніж 2 запити.
користувач1496062

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


2

У коді спочатку ви можете мати декілька DBContext і лише одну базу даних. Вам просто потрібно вказати рядок з'єднання в конструкторі.

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

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

Так, ви можете, але як можна робити запити від різних об'єктів з різних db-контекстів?
Реза

2

Ще трохи «мудрості». У мене база даних, що стосується обох, Інтернету та внутрішнього додатка. У мене є контекст для кожного обличчя. Це допомагає мені зберігати дисципліновану, захищену сегрегацію.


1

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

У мене є рішення з двома базами даних. Один призначений для даних домену, крім інформації про користувача. Інший призначений виключно для інформації користувачів. Цей підрозділ передусім керується Загальним регламентом ЄС про захист даних . Маючи дві бази даних, я можу вільно переміщати дані домену (наприклад, з Azure в моє середовище розробки) до тих пір, поки дані користувача залишаються в одному безпечному місці.

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


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

0

Так, витратив досить багато часу на проблему з окремими контекстами БД для кожної схеми БД, сподіваюся, що це допоможе комусь іншому ...

Нещодавно я почав працювати над проектом, який мав одну базу даних з 3 схемами (перший підхід до БД), одну з них для управління користувачами. З кожної окремої схеми існував контекст БД. Звичайно, користувачі були пов'язані і з іншими схемами, наприклад. схема KB мала тему таблиці, яка була "створена", "остання зміна" тощо. FK для схеми ідентичності, аплікатора таблиці.

Ці об'єкти завантажувались окремо в C #, по-перше, тема завантажувалася з 1 контексту, потім користувачі завантажувались через ідентифікатори користувачів з іншого контексту db - не приємно, треба це виправити! (подібно до використання декількох dbcontexts в одній базі даних з EF 6 )

По-перше, я спробував додати відсутні схеми команди FK від схеми ідентичності до схеми KB, до EF modelBuilder в контексті КБ DB. Так само, як якщо б був лише 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. Вибір оператора не мав належної інформації про схему, назви полів тощо.

Я майже здався, але тоді я помітив перемикач "-d" під час бігу dotnet ef dbcontext scaffold. Його коротке значення для -data-анотацій - Використовуйте атрибути для налаштування моделі (де це можливо). У разі відсутності використовується лише вільний API. За допомогою вказаного перемикача властивості об'єкта визначалися не в db контексті OnModelCreating(), а на самому об'єкті з атрибутами.

Таким чином, EF отримав достатню інформацію для створення належного оператора SQL з власними іменами полів та схемами.

TL; DR: окремі контексти БД добре не обробляють відносини (FK) між ними, кожен контекст містить лише інформацію про власні сутності. Коли вказується "-data-annotations" dotnet ef dbcontext scaffold, ці відомості зберігаються не в кожному окремому контексті, а в самих об'єктах DB.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.