Чому я не повинен використовувати шаблон репозиторію з Entity Framework?


203

Під час співбесіди від мене попросили пояснити, чому схема сховища не є гарною схемою для роботи з ОРМ, як Entity Framework. Чому це так?


60
це було хитромудрі питання
Ому

2
Я, мабуть, відповів інтерв'юеру, що Microsoft дуже часто використовує шаблон репозиторію, коли вони демонструють структуру сутності: | .
Лоран Буро-Рой

1
Отже, що було причиною того, що інтерв'юер був недоброю ідеєю?
Боб Хорн

3
Найсмішніший факт полягає в тому, що пошук "шаблону сховища" в Google дає результати, які пов'язані в основному з Entity Framework та способом використання шаблону з EF.
Арсеній Муренко

2
перевірити блог ayende ayende.com/blog . На основі того, що я знаю, він використовував шаблон сховища, але врешті-решт відмовився від нього на користь шаблону об'єкта запиту
Jaime Sangcap

Відповіді:


99

Я не бачу жодної причини, щоб шаблон репозиторію не працював з Entity Framework. Шаблон сховища - це рівень абстракції, який ви розміщуєте на рівні доступу до даних. Вашим шаром доступу до даних може бути все - від чистих процедур, що зберігаються в ADO.NET, до Entity Framework або файлу XML.

У великих системах, де у вас є дані, що надходять з різних джерел (база даних / XML / веб-сервіс), добре мати рівень абстракції. Шаблон сховища добре працює в цьому сценарії. Я не вірю, що Entity Framework є достатньою абстракцією, щоб приховати те, що відбувається за лаштунками.

Я використовував шаблон репозиторію з Entity Framework в якості методу рівня доступу до даних, і я ще не стикаюся з проблемою.

Ще одна перевага абстрагування DbContextрепозиторію - тестування одиниць . Ви можете мати свій IRepositoryінтерфейс, до якого є 2 реалізації, одна (справжня сховище), яка використовує DbContextдля спілкування з базою даних, і друга, FakeRepositoryяка може повертати об'єкти пам'яті / знущаються дані. Це робить ваш IRepositoryблок опробовуваним, таким чином, інші частини коду, які використовуються IRepository.

public interface IRepository
{
  IEnumerable<CustomerDto> GetCustomers();
}
public EFRepository : IRepository
{
  private YourDbContext db;
  private EFRepository()
  {
    db = new YourDbContext();
  }
  public IEnumerable<CustomerDto> GetCustomers()
  {
    return db.Customers.Select(f=>new CustomerDto { Id=f.Id, Name =f.Name}).ToList();
  }
}
public MockRepository : IRepository
{
  public IEnumerable<CustomerDto> GetCustomers()
  {
    // to do : return a mock list of Customers
    // Or you may even use a mocking framework like Moq
  }
}

Тепер, використовуючи DI, ви отримуєте реалізацію

public class SomeService
{
  IRepository repo;
  public SomeService(IRepository repo)
  {
     this.repo = repo;
  }  
  public void SomeMethod()
  {
    //use this.repo as needed
  }    
}

3
Я не сказав, що це не спрацює, я також працював з шаблоном репозиторіїв з EF, але сьогодні мене запитали, чому НЕ ДОБРЕ використовувати шаблон із DataBase, програмою, що використовує Database

2
Гаразд, оскільки це найпопулярніша відповідь, я вибрав її як правильну відповідь

65
Коли останній раз найпопулярніший == був правильний?
HDave

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

7
ColacX. Ми спробували саме це - DBcontext прямо в шарі контролера - і ми повертаємося до шаблону репо. З малюнком Repo, модульні тести йшли від масивного глузування DbContext, що постійно провалювався. EF було важким у використанні, крихким і витрачало години на дослідження нюансів EF. Зараз у нас є невеликі прості макети репо. Код чистіший. Розмежування роботи чіткіше. Я більше не згоден з натовпом, що EF - це вже модель репо і вже перевірена одиниця.
Rhyous

434

Єдиний найкращий привід не використовувати шаблон репозиторію з Entity Framework? Entity Framework вже реалізує шаблон репозиторію. DbContext- це ваш UOW (Unit of Work) і кожен DbSet- сховище. Нанесення іншого шару поверх цього не тільки є зайвим, але ускладнює обслуговування.

Люди дотримуються шаблонів, не усвідомлюючи мети цього шаблону. У випадку шаблону репозиторію метою є абстрагування логіки запитів бази даних низького рівня. За старих часів фактичного запису SQL-заяв у свій код, шаблон репозиторію був способом перемістити цей SQL з окремих методів, розпорошених по вашій кодовій базі та локалізувати його в одному місці. Наявність такої ORM, як Entity Framework, NHibernate тощо, є заміною цього абстрагування коду, і як таке відміняє потребу в шаблоні.

Однак, це не погана ідея створити абстракцію поверх вашої ОРМ, просто не так складно, як UoW / репозиторія. Я б сформулював схему обслуговування, де ви створюєте API, який може використовувати ваша програма, не знаючи і не піклуючись про те, чи надходять дані з Entity Framework, NHibernate або веб-API. Це набагато простіше, оскільки ви просто додаєте методи до свого класу обслуговування, щоб повернути дані, які потрібні вашій програмі. Якщо ви, наприклад, писали додаток "Зобов’язання", у вас може виникнути службовий дзвінок щодо повернення предметів, які належать до цього тижня та ще не завершені. Все, що ваша програма знає, це те, що якщо вона хоче цю інформацію, вона називає цей метод. Всередині цього методу та у вашій службі взагалі ви взаємодієте з Entity Framework або будь-яким іншим, чим ви користуєтесь. Потім, якщо пізніше ви вирішите переключити ORM або витягнути інформацію з веб-API,

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


68
Це здається єдиною правильною відповіддю.
Майк Чемберлен

10
Ви можете знущатися DbContextз EF6 + (див.: Msdn.microsoft.com/en-us/data/dn314429.aspx ). Навіть в менших версіях, ви можете використовувати підроблені DbContext-like клас з знущався DbSetз, так як DbSetреалізує iterface, IDbSet.
Кріс Пратт

14
@TheZenker, можливо, ви точно не дотримувались шаблону сховища. Найсуворіша різниця - повернене значення. Репозиторії повертають запитові дані, тоді як служби повинні повертати перелічні дані. Навіть це не так вже й чорно-біле, тому що там є деякі перекриття. Це більше в тому, як ви цим користуєтеся. Репозиторій повинен просто повернути набір усіх об'єктів, до яких ви далі подаєте запит, в той час як сервіс повинен повернути остаточний набір даних, і не повинен підтримувати подальші запити.
Кріс Пратт

10
Ризикуючи звучати егоїстично: вони помиляються. Тепер, що стосується офіційних навчальних посібників, Microsoft відступила, використовуючи сховища, від того, що я бачила з EF6. Щодо книги, я не можу говорити, чому автор вирішив використовувати сховища. Що я можу говорити, як хтось із траншей, що створюють масштабні програми, - це те, що використання шаблону сховища з Entity Framework є кошмаром технічного обслуговування. Як тільки ви переходите до чогось складнішого, ніж у кілька сховищ, ви закінчуєте витрачати надзвичайно багато часу на управління своїми сховищами / одиницями роботи.
Кріс Пратт

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

45

Ось один взяття від Айенде Рахієн: Архітектура в ямі дум: Зло шару абстракції сховища

Я ще не впевнений, чи згоден я з його висновком. Це привід 22 - з одного боку, якщо я обертаю свій контекст EF у сховищах, характерних для типу методами пошуку даних, специфічних для запитів, я фактично можу перевірити свій код (тип), що майже неможливо з Entity Тільки рамки. З іншого боку, я втрачаю здатність робити багатий запит і семантичне підтримання відносин (але навіть коли я маю повний доступ до цих функцій, я завжди відчуваю, що ходжу по яєчним шкаралупам навколо EF або будь-який інший ORM, який я можу вибрати , оскільки я ніколи не знаю, якими методами його реалізація IQueryable може чи не може підтримувати, чи інтерпретуватиме моє додавання до колекції властивостей навігації як створення або просто асоціацію, чи буде це ледачий чи прагнутий навантаження чи взагалі не завантажується за замовчуванням тощо, тому, можливо, це на краще. Об'єктно-реляційне об'єктивне "відображення" з нульовим імпедансом - це щось міфологічне створіння - можливо, саме тому останній випуск Entity Framework був кодований під назвою "Чарівний Єдиноріг").

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

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

Оновлення: Я мав певний успіх у використанні постачальника зусиль для Entity Framework. Зусилля - це провайдер пам'яті (відкритий код), який дозволяє використовувати EF у тестах саме так, як ви використовували б його щодо реальної бази даних. Я розглядаю можливість переключення всіх тестів у цьому проекті, над яким я працюю, щоб скористатися цим провайдером, оскільки, здається, все набагато простіше. Це єдине рішення, яке я знайшов до цих пір, яке стосується всіх проблем, про які я раніше шумував. Єдине, що при запуску моїх тестів є невелика затримка, оскільки для створення бази даних в пам'яті (для цього використовується інший пакет під назвою NMemory), але я не вважаю це справжньою проблемою. Там є стаття Code Code, яка розповідає про використання Effort (порівняно з SQL CE) для тестування.


3
Будь-яка стаття архітектури, не зазначаючи тестовий блок, автоматично надсилається в кошик для сміття. Один з моментів шаблону сховища - здобути певну здатність до тесту.
Сон Сміт

3
Ви все ще можете провести одиничні тести без упаковки контексту EF (який вже є сховищем). Ви повинні перевірити свій домен / послуги, а не запити до бази даних (це інтеграційні тести).
Даніель Літтл

2
Заповітність EF значно покращилась у версії 6. Ви можете повністю знущатися DbContext. Незважаючи на те, ви завжди можете знущатися DbSet, і це все-таки м'ясо Entity Framework. DbContextце трохи більше, ніж клас для розміщення ваших DbSetвластивостей (сховищ) в одному місці (одиниця роботи), особливо в контексті тестування одиниць, де всі ініціалізація бази даних та з'єднання не потрібні і не потрібні.
Кріс Пратт

Втратити навігацію щодо пов’язаних об'єктів погано і протидіє OOP, але ви матимете більше контролю над тим, що запитується.
Аліреза

До моменту тестування, EF Core пройшов довгий шлях із вичерпаними In-Memory та In-Memory з постачальниками Sqlite, щоб дозволити тестування одиниць. Принесіть докер, коли вам потрібно тестування інтеграції для запуску тестів на контейнерній базі даних.
Sudhanshu Mishra

16

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


Скажіть, будь ласка, деякі переваги для "Entity Framework надає вам безліч кодуючих та функціональних переваг"?
ManirajSS

2
це він мав на увазі. var id = Entity.Where (i => i.Id == 1337) .Single () інкапсулював і загорнув це у сховище, ви, в основному, не можете виконувати подібну логіку запитів зовні, що або змушує вас додати більше коду до сховище та інтерфейс для отримання ідентифікатора. B поверніть контекст сутності з сховища, щоб ви могли написати логіку запиту (що є просто дурницею)
ColacX

14

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

Перегляд шаблону сховища


Стаття мені сподобалась, але IMHO для корпоративних програм, шар абстракції між DAL та Bl MUST Have, оскільки ви не могли знати, що саме будете використовувати завтра. Але дякую за те, що поділилися посиланням

1
Хоча особисто я думаю, що це стосується напр. NHibernate ( ISessionFactoryі ISessionвони легко піддаються гнучкості), але DbContext, на жаль , це не так просто
Patryk Ćwiek

6

Дуже вагомою причиною для використання шаблону репозиторію є дозвіл відокремити вашу логіку бізнесу та / або інтерфейс користувача від System.Data.Entity. Цьому є чимало переваг, включаючи реальні переваги при тестуванні одиниць, дозволяючи йому використовувати Fakes або Mocks.


Я згоден з цією відповіддю. Мої сховища - це лише методи розширення, які не створюють нічого, крім створення дерев вираження. Над ДУЖЕ простою абстракцією, яка просто забезпечує загальну функціональність безпосередньо над вершиною dbcontext. Єдина реальна мета абстракції - зробити IoC трохи легшим. Я думаю, що люди намагаються робити те, що вони не повинні робити у своїх сховищах. Вони роблять репо за кожним суб'єктом господарювання або вкладають туди ділову логіку, яка повинна бути на рівні послуг. Вам потрібен лише один простий загальний репо. Це не потрібно, просто забезпечує стабільний інтерфейс.
Брендон

Ще одне, що я просто хотів додати. Так, CQRS є надзвичайно чудовою методологією у випадках, що стосуються MOST. Для деяких клієнтів, над якими я працював, коли хлопці з бази даних не працюють добре з розробниками (що трапляється частіше, ніж можна було б думати, особливо в банках), EF над SQL - найкращий варіант. У тому конкретному сценарії, коли ви абсолютно не контролюєте свою базу даних, модель сховища має сенс. Оскільки близько нагадує структуру даних і легко перекладати те, що відбувається в базі даних, і навпаки. На мою думку, це дійсно політичне та логістичне рішення. Щоб заспокоїти богів БД.
Брендон

1
Я фактично починаю ставити під сумнів свої попередні думки з цього приводу. EF - це комбінована схема одиниці роботи та сховища. Як згадував Кріс Пратт вище в EF6, ви можете легко знущатися над об'єктами контексту та DbSet. Я все ще вважаю, що доступ до даних повинен бути загорнутий у класи, щоб захистити класи ділової логіки від фактичного механізму доступу до даних, але переходити цілу свиню та обгортати EF з іншого сховища та абстрагування блоку роботи здається надмірним.
James Culshaw

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

@jcmcbeth, якщо ви подивитесь на мій коментар безпосередньо над вашим, ви побачите, що я змінив свою первісну думку щодо структури сховища та EF.
Джеймс Калшоу

0

У нас виникли проблеми з повторюваними, але різними випадками DbContext Entity Framework DbContext, коли контейнер IoC, який створює нові () репозиторії кожного типу (наприклад, UserRepository та GroupRepository, що кожного виклику власного IDbSet з DBContext), іноді може викликати кілька контекстів на запит (у контексті MVC / веб).

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


Я стикався з цією проблемою в декількох різних проектах.
ColacX

0

Після випробування шаблону репозиторію на невеликому проекті я настійно раджу не використовувати його; не тому, що це ускладнює вашу систему, і не тому, що глузування з даних є кошмаром, а тому, що тестування стає марним !!

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

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

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

Репозиторій був найкращою практикою в ті дні, коли ми використовували XBase, AdoX та Ado.Net, але з сутністю !! (Сховище над сховищем)

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


1
За винятком того, що ви НЕ хочете перевіряти поведінку бази даних в одиничних тестах, оскільки це зовсім не такий рівень тестування.
Маріуш Джамро

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

-3

Це пов'язано з міграцією: неможливо змусити переходити до роботи, оскільки рядок з'єднання знаходиться в web.config. Але, DbContext знаходиться в шарі сховища. IDbContextFactory повинен мати конфігураційний рядок до бази даних. Але немає можливості, щоб міграція отримувала рядок з'єднання з web.config.

Є робота навколо, але я ще не знайшов чистого рішення для цього!

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