Есть ли реальное преимущество для общего репозитория?

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?


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

Сэм
источник
3
что - то интересное ayende.com/blog/3955/repository-is-the-new-singleton
Kenny
1
Является ли вопрос действительно об «продвижении универсального репозитория» или более «как скрыть сложные запросы за универсальным интерфейсом репозитория»? Можно ли быть универсальным, если его интерфейс и использование зависят от linq? Наш Repositoryapi имеет метод QueryByExample, который полностью независим от метода поиска и позволит изменить реализацию.
k3b
«Это позволяет мне использовать один и тот же репозиторий, чтобы делать несколько вещей для нескольких разных типов сущностей» => Это я или вы просто неправильно поняли, что такое универсальный репозиторий (в том числе в отношении упомянутой статьи)? Для меня универсальный репозиторий всегда означал создание шаблонов для всех репозиториев с одним классом или интерфейсом, без единого экземпляра репозитория, управляющего постоянством всех сущностей независимо от их типа ...
guillaume31

Ответы:

35

Универсальный репозиторий даже бесполезен (и IMHO также плох) для Entity Framework. Это не приносит никакой дополнительной ценности к тому, что уже предоставлено IDbSet<T>(что является кстати универсальным репозиторием).

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

Второй распространенный аргумент об упрощенном модульном тестировании также неверен, потому что фиктивный репозиторий / набор с хранением данных в памяти заменяет провайдера Linq другим, у которого есть другие возможности. Поставщик Linq-to-entity поддерживает только подмножество функций Linq - он даже не поддерживает все методы, доступные в IQueryable<T>интерфейсе. Совместное использование деревьев выражений между уровнем доступа к данным и уровнями бизнес-логики предотвращает подделку уровня доступа к данным - логика запросов должна быть разделена.

Если вы хотите иметь сильную «общую» абстракцию, вы должны задействовать и другие шаблоны. В этом случае вам нужно использовать абстрактный язык запросов, который может быть переведен репозиторием на определенный язык запросов, поддерживаемый используемым уровнем доступа к данным. Это обрабатывается шаблоном спецификации. Linq on IQueryable- это спецификация (но для перевода требуется поставщик - или какой-то пользователь, переводящий дерево выражений в запрос), но вы можете определить свою собственную упрощенную версию и использовать ее. Например, NHibernate использует Criteria API. Тем не менее, самый простой способ - использовать определенный репозиторий с конкретными методами. Этот способ проще всего реализовать, проще всего тестировать и проще всего подделывать в модульных тестах, поскольку логика запросов полностью скрыта и отделена за абстракцией.

Ладислав Мрнка
источник
ISessionВкратце , у NHibernate есть простое средство для общих юнит-тестирований, и я бы с радостью отбросил «хранилище» при работе с EF - но есть ли более простой и простой способ воссоздать это? Что-то сродни ISessionтому ISessionFactory, потому что, IDbContextнасколько я могу судить , нет ничего ...
Патрик Свик,
Нет, нет IDbContext- если вы хотите, IDbContextвы можете просто создать его и реализовать в своем производном контексте.
Ладислав Мрнка
Итак, вы говорите, что для каждодневных приложений MVC IDbSet <T> должен обеспечить достаточно хороший общий репозиторий, чтобы полностью забыть о шаблоне репозитория. За исключением особых ситуаций, например, когда вам не разрешены ссылки DAL в вашем проекте MVC.
ProfK
1
Хороший ответ. Некоторые разработчики создают просто еще один слой абстракций без какой-либо причины. ИМО, EF не нужны ни общий репозиторий, ни единица работы (они
встроены
1
IDbSet <T> является универсальным репозиторием с зависимостью от Entity Framework, что отчасти противоречит цели использования универсального репозитория для абстрагирования зависимости от Entity Framework.
Джоэл МакБет
7

Проблема не в шаблоне хранилища. Хорошая абстракция между получением данных и их получением.

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

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

Telastyn
источник
Поэтому имеет смысл иметь репозиторий для каждого объекта данных или группу тесно связанных объектов с конкретными методами, такими как GetById (int id), SortedList () и т. Д.? Возвращаю ли я списки объектов данных из хранилища или преобразовываю их в бизнес-объекты с необходимыми полями? В некотором роде думали, что это произошло на уровне Сервис / Бизнес.
Сэм
1
@sam - да, я предпочитаю более конкретные репозитории. Если они делают перевод или нет, это зависит от того, где что-то может измениться. Если ваш домен четко определен, я бы вернул вещи в форме домена. Если данные хорошо определены, я бы вернул вещи в форме структур данных. Если ни того, ни другого, у меня будет сущность, которая служит четко определенной основой для построения, а затем адаптируется к ней.
Теластин
1
Нет причин, по которым у вас не может быть определенных репозиториев, которые делегируют общие вещи в общий репозиторий, но также есть дополнительные специфичные для репозитория методы для более сложных вызовов. Я видел, как это делалось несколько раз.
Эрик Кинг,
@EricKing У меня тоже есть, и там было хорошо, но он имеет тенденцию пропускать некоторую абстракцию, поскольку общие вещи, как правило, существуют только из-за общности того, как хранятся данные (например, для GetByID требуются таблицы с идентификаторами).
Теластин
@Telastyn Да, я бы с этим согласился, но это происходит и с конкретными репозиториями. Утечка абстракций не характерна для общих репозиториев. (Ух ты, не мог бы я сформулировать это более неуклюже?)
Эрик Кинг,
2

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

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

Наиболее эффективный способ создания общего слоя данных - это заранее знать различные типы источников данных, которые приложение будет использовать. Как вы видели, предполагать, что LINQ или SQL универсальны, может быть проблемой. Попытка модернизировать новые хранилища данных, вероятно, приведет к переписыванию.

[Редактировать: Добавлено следующее.]

Это также зависит от того, что требуется приложению от уровня данных. Если приложение просто загружает или хранит объекты, уровень данных может быть очень простым. По мере того, как необходимость в поиске, сортировке и фильтрации возрастает, сложность уровня данных возрастает, и абстракции начинают становиться дырявыми (например, выставление запросов LINQ в вопросе). Однако, как только пользователи могут предоставлять свои собственные запросы, необходимо тщательно взвесить затраты / выгоды от уровня данных.

Актон
источник
1

Наличие слоя кода над базой данных целесообразно практически во всех случаях. Как правило, я бы предпочел шаблон «GetByXXXXX» в указанном коде - он позволяет оптимизировать запросы за ним по мере необходимости, сохраняя при этом интерфейс пользователя свободным от беспорядка интерфейса данных.

Воспользоваться дженериками - это, безусловно, честная игра - Load<T>(int id)метод имеет массу смысла. Но построение репозиториев вокруг LINQ - это эквивалент 2010-го года для отбрасывания SQL-запросов везде с небольшой добавленной безопасностью типов.

Уайетт Барнетт
источник
0

Что ж, по предоставленной ссылке я вижу, что это может быть удобная оболочка для a DataServiceContext, но она не уменьшает манипуляций с кодом и не улучшает читабельность. Кроме того, доступ к нему DataServiceQuery<T>ограничен, что ограничивает гибкость .Where()и .Single(). Ни AddRange()альтернативы, ни предоставленные. Также не Delete(Predicate)предусмотрено, что может быть полезно ( repo.Delete<Person>( p => p.Name=="Joe" );чтобы удалить Джо). И т.п.

Вывод: такой API препятствует нативному API и ограничивает его несколькими простыми операциями.

aiodintsov
источник
Ты прав. Это была не статья, использующая шаблон, который я использовал в моем примере кода. Постараюсь найти ссылку, когда вернусь домой.
Сэм
Обновленная ссылка добавлена ​​внизу вопроса.
Сэм
-1

Я бы сказал "Да". В зависимости от вашей модели данных, хорошо разработанный Generic Repository может сделать уровень доступа к данным более гибким, аккуратным и намного более устойчивым.

Прочитайте эту серию статей (автор @ chris-pratt ):

Tohid
источник