Чи є реальна перевага для загального сховища?


28

Читав деякі статті про переваги створення загальних сховищ для нового додатка ( приклад ). Ідея здається приємною, оскільки дозволяє мені використовувати одне сховище, щоб робити кілька речей для декількох типів сутності одночасно:

IRepository repo = new EfRepository(); // Would normally pass through IOC into constructor 
var c1 = new Country() { Name = "United States", CountryCode = "US" };
var c2 = new Country() { Name = "Canada", CountryCode = "CA" };
var c3 = new Country() { Name = "Mexico", CountryCode = "MX" };
var p1 = new Province() { Country = c1, Name = "Alabama", Abbreviation = "AL" };
var p2 = new Province() { Country = c1, Name = "Alaska", Abbreviation = "AK" };
var p3 = new Province() { Country = c2, Name = "Alberta", Abbreviation = "AB" };
repo.Add<Country>(c1);
repo.Add<Country>(c2);
repo.Add<Country>(c3);
repo.Add<Province>(p1);
repo.Add<Province>(p2);
repo.Add<Province>(p3);
repo.Save();

Однак решта реалізації Репозиторію дуже покладається на Linq:

IQueryable<T> Query();
IList<T> Find(Expression<Func<T,bool>> predicate);
T Get(Expression<Func<T,bool>> predicate);
T First(Expression<Func<T,bool>> predicate);
//... and so on

Ця модель репозиторію спрацювала фантастично для Entity Framework і в значній мірі запропонувала 1 - 1 відображення методів, доступних у DbContext / DbSet. Але зважаючи на повільне використання Linq для інших технологій доступу до даних за межами Entity Framework, яку перевагу це забезпечує над роботою безпосередньо з DbContext?

Я спробував написати PetaPoco версію Repository, але PetaPoco не підтримує Linq виразів, що дозволяє створювати загальний IRepository інтерфейс досить багато непотрібної , якщо ви тільки не використовувати його для основного GETALL, GetByID, додавання, оновлення, видалення та Зберегти методи та використовувати його як базовий клас. Тоді вам доведеться створити конкретні сховища зі спеціалізованими методами, щоб обробити всі пропозиції "куди", які я раніше міг би передати як предикат.

Чи корисна модель загального репозиторію для чогось поза межами Entity Framework? Якщо ні, то чому б хтось використовував це взагалі, а не працював безпосередньо з Entity Framework?


Оригінальне посилання не відображає шаблон, який я використовував у своєму зразку коду. Ось ( оновлене посилання ).



1
Чи справді виникає питання про "модернізацію загального сховища" чи це більше "як приховати складні запити за загальним інтерфейсом сховища"? Чи можливо бути загальним, якщо його інтерфейс та використання залежать від linq? У нашому Репозиторії є метод QueryByExample, який повністю не залежить від техніки пошуку і дозволить змінити реалізацію.
k3b

"Це дозволяє мені використовувати одне сховище, щоб зробити кілька речей для декількох типів сутності" => Це я чи ви просто неправильно зрозуміли, що таке загальне сховище (у тому числі стосовно згаданої статті)? Для мене загальний репозиторій завжди означав шаблонування всіх репост з одним класом або інтерфейсом, не маючи жодного екземпляра сховища, який керував би стійкістю всіх об'єктів незалежно від їх типу ...
guillaume31,

Відповіді:


35

Загальне сховище навіть марно (і IMHO також погано) для Entity Framework. Це не приносить ніякої додаткової цінності тому, що вже надається IDbSet<T>(що є btw. Generic repository).

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

Другий поширений аргумент щодо спрощеного тестування одиниць також є помилковим, оскільки глузування з сховища / набору даних у пам'яті зберігає дані провайдера Linq на іншого, який має інші можливості. Провайдер Linq-to-Entities підтримує лише підмножину функцій Linq - він навіть не підтримує всі методи, доступні в IQueryable<T>інтерфейсі. Спільний доступ до дерев виразів між шаром доступу до даних та шарами бізнес-логіки запобігає будь-якій підробці шару доступу до даних - логіку запиту потрібно розділяти.

Якщо ви хочете мати сильну "загальну" абстракцію, ви повинні також включати інші зразки. У цьому випадку вам потрібно використовувати абстрактну мову запиту, яку можна перекласти сховищем на певну мову запиту, підтримувану використовуваним рівнем доступу до даних. Це обробляється за схемою специфікацій. Linq on IQueryable- це специфікація (але для перекладу потрібен постачальник - або якесь власне відвідувач, що перекладає дерево виразів у запит), але ви можете визначити власну спрощену версію та використовувати її. Наприклад, NHibernate використовує API критеріїв. Але найпростішим способом є використання конкретного сховища за допомогою конкретних методів. Цей спосіб є найпростішим у здійсненні, найпростішим для тестування та найпростішим підробкою в одиничних тестах, оскільки логіка запиту повністю прихована і розділена за абстракцією.


Лише швидке запитання, у NHibernate є те, ISessionщо легко можна підмітити для загальних цілей тестування одиниць, і я б із задоволенням скинув «сховище» під час роботи з EF - але чи є простіший, простіший спосіб відтворити це? Щось схоже на ISessionі те ISessionFactory, бо немає IDbContext, наскільки я можу сказати ...
Patryk Ćwiek

Ні, немає IDbContext- якщо ви хочете, IDbContextви можете просто створити його та реалізувати його у похідному контексті.
Ладислав Мрнка

Отже, ви говорите, що для щоденних програм MVC IDbSet <T> повинен забезпечити достатньо хороший загальний сховище, щоб повністю забути про шаблон сховища. За винятком особливих ситуацій, наприклад, якщо у вашому проекті MVC заборонено використовувати будь-які посилання на DAL.
ПрофК

1
Гарна відповідь. Деякі розробники створюють ще один шар абстракцій без будь-яких причин. IMO, EF не потребують ні загального сховища, ні одиниці роботи (вони вбудовані)
ашраф

1
IDbSet <T> - це загальний репозиторій із залежністю від Entity Framework, який перемагає мету використання загального сховища для усунення залежності від Entity Framework.
Джоел МакБет

7

Проблема не є шаблоном сховища. Хороша річ, щоб абстрагуватися між отриманням даних та тим, як ви їх отримуєте.

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

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


Отже, має сенс мати сховище на об’єкт даних або групу тісно пов'язаних об'єктів із конкретними методами, такими як GetById (int id), SortedList () тощо? Чи повертаю я списки об’єктів даних із сховища або перетворюю їх у бізнес-об’єкти з необхідними полями? Kinda подумав, що саме так сталося на рівні Сервіс / Бізнес.
Сем

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

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

@EricKing У мене теж є, і це було добре там, але воно, як правило, просочується деякою абстракцією, оскільки звичайний матеріал має тенденцію існувати лише через спільність того, як зберігаються дані (GetByID, наприклад, вимагає таблиць з ідентифікаторами).
Теластин

@Telastyn Так, я б погодився з цим, але це трапляється і з конкретними сховищами. Витікаючі абстракції не характерні для загальних сховищ. (Нічого собі, чи могла б я так незграбно сформулювати фразу?)
Ерік Кінг

2

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

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

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

[Редагувати: Додано наступне.]

Це також залежить від того, що потрібно додатку від рівня даних. Якщо програма лише завантажує або зберігає об'єкти, рівень даних може бути дуже простим. Зі збільшенням необхідності пошуку, сортування та фільтрування зростає складність рівня даних, а абстракції починають протікати (наприклад, викриття запитів LINQ у запитанні). Після того як користувачі можуть надати власні запити, вартість / вигода рівня даних потрібно ретельно зважити.


1

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

Користуватися дженериками, безумовно, є чесною грою - мати Load<T>(int id)метод має багато сенсу. Але створення сховищ навколо LINQ - це еквівалент 2010-х років, що випадає з запитів sql скрізь із невеликою безпекою типу.


0

Ну, за наданим посиланням я можу побачити, що це може бути зручна обгортка для DataServiceContext, але не зменшує маніпуляцій з кодом і не покращує читабельність. Крім того, доступ до DataServiceQuery<T>перешкоджає, обмежуючи гнучкість до .Where()та .Single(). Не передбачені AddRange()альтернативи та альтернативи. Не Delete(Predicate)надається, що може бути корисно ( repo.Delete<Person>( p => p.Name=="Joe" );для видалення Joe-s). І т.д.

Висновок: такий API перешкоджає нативному API і обмежує його кількома простими операціями.


Ви праві. Це не стаття, використовуючи зразок, який я маю в своєму зразковому коді. Спробую знайти посилання, коли я повернусь додому.
Сем

Оновлене посилання додано внизу питання.
Сем

-1

Я б сказав "Так". Залежно від вашої моделі даних, добре розроблений загальний репозиторій може зробити рівень доступу до даних більш високим, акуратним та набагато надійнішим.

Прочитайте цю серію статей (від @ chris-pratt ):

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