Читал некоторые статьи о преимуществах создания универсальных репозиториев для нового приложения ( пример ). Идея кажется хорошей, потому что она позволяет мне использовать один и тот же репозиторий для одновременного выполнения нескольких операций с несколькими различными типами сущностей:
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?
Исходная ссылка не отражает шаблон, который я использовал в моем примере кода. Вот ( обновленная ссылка ).
Ответы:
Универсальный репозиторий даже бесполезен (и 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
вы можете просто создать его и реализовать в своем производном контексте.Проблема не в шаблоне хранилища. Хорошая абстракция между получением данных и их получением.
Проблема здесь заключается в реализации. Предполагать, что произвольное выражение будет работать для фильтрации, в лучшем случае рискованно.
Создание хранилища для всех ваших объектов напрямую упускает из виду. Объекты данных редко, если вообще когда-либо отображаются на бизнес-объекты. Передача T для фильтрации имеет гораздо меньше смысла в таких ситуациях. А предоставление такой большой функциональности в значительной степени гарантирует, что вы не сможете поддерживать все это, когда придет другой поставщик.
источник
Значение общего уровня данных (хранилище - это особый тип уровня данных) позволяет коду изменять базовый механизм хранения, практически не влияя на вызывающий код.
В теории это работает хорошо. На практике, как вы заметили, абстракция часто протекает. Механизмы, используемые для доступа к данным в одном, отличаются от механизмов в другом. В некоторых случаях вы пишете код дважды: один раз на бизнес-уровне и повторяете его на уровне данных.
Наиболее эффективный способ создания общего слоя данных - это заранее знать различные типы источников данных, которые приложение будет использовать. Как вы видели, предполагать, что LINQ или SQL универсальны, может быть проблемой. Попытка модернизировать новые хранилища данных, вероятно, приведет к переписыванию.
[Редактировать: Добавлено следующее.]
Это также зависит от того, что требуется приложению от уровня данных. Если приложение просто загружает или хранит объекты, уровень данных может быть очень простым. По мере того, как необходимость в поиске, сортировке и фильтрации возрастает, сложность уровня данных возрастает, и абстракции начинают становиться дырявыми (например, выставление запросов LINQ в вопросе). Однако, как только пользователи могут предоставлять свои собственные запросы, необходимо тщательно взвесить затраты / выгоды от уровня данных.
источник
Наличие слоя кода над базой данных целесообразно практически во всех случаях. Как правило, я бы предпочел шаблон «GetByXXXXX» в указанном коде - он позволяет оптимизировать запросы за ним по мере необходимости, сохраняя при этом интерфейс пользователя свободным от беспорядка интерфейса данных.
Воспользоваться дженериками - это, безусловно, честная игра -
Load<T>(int id)
метод имеет массу смысла. Но построение репозиториев вокруг LINQ - это эквивалент 2010-го года для отбрасывания SQL-запросов везде с небольшой добавленной безопасностью типов.источник
Что ж, по предоставленной ссылке я вижу, что это может быть удобная оболочка для a
DataServiceContext
, но она не уменьшает манипуляций с кодом и не улучшает читабельность. Кроме того, доступ к немуDataServiceQuery<T>
ограничен, что ограничивает гибкость.Where()
и.Single()
. НиAddRange()
альтернативы, ни предоставленные. Также неDelete(Predicate)
предусмотрено, что может быть полезно (repo.Delete<Person>( p => p.Name=="Joe" );
чтобы удалить Джо). И т.п.Вывод: такой API препятствует нативному API и ограничивает его несколькими простыми операциями.
источник
Я бы сказал "Да". В зависимости от вашей модели данных, хорошо разработанный Generic Repository может сделать уровень доступа к данным более гибким, аккуратным и намного более устойчивым.
Прочитайте эту серию статей (автор @ chris-pratt ):
источник