Репозиторий / IQueryable / Объект Запроса

Я создаю репозиторий, и я видел во многих местах 2 причины не выставлять IQueryable вне репозитория.

1) Во-первых, потому, что разные поставщики LINQ могут вести себя по-разному, и эта разница должна содержаться в репозитории.

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

Я думаю, что проблема 2 может быть предотвращена только сохраняя всю логику запросов в репозитории и не допуская никакой формы построения внешних запросов? Но мне это кажется немного непрактичным.

Проблема 1, похоже, будет решена с помощью шаблона объекта данных.

например public IEnumerable<T> FindBy(Query query)

мой вопрос в том, почему бы мне просто не передать лямбда-выражение, поскольку это независимый от поставщика, казалось бы, предоставляет мне ту же функциональность, что и объект запроса, и тот же уровень разлука?

например public IEnumerable<T> FindBy(Expression<Func<T,bool>> predicate)

есть ли причина не делать этого? Это нарушает какие-то правила? Лучшие практики? о чем я должен знать?

2 ответов


просто вернуть IQueryable<T>.

прежде чем вы напишете еще один бит "кода репозитория", вы значительно выиграете от чтения статьи Ayende Architecting in The Pit of Doom-зло слоя абстракции хранилища

ваш подход, без сомнения, добавляет значительную ненужную сложность.

весь код из другого вопроса в общий список OrderBy лямбда не удается ничего сделать кроме маски существующего эффективного API с ненужной и незнакомой абстракцией.

что касается ваших двух проблем,

  1. поставщики LINQ ведут себя по-разному, но пока предикаты, которые вы передаете, могут обрабатываться поставщиком LINQ, это не имеет значения. В противном случае, вы все равно столкнетесь с той же проблемой, потому что вы передаете в Expression, который передается на IQueryable в итоге все равно. Если IQueryProvider реализация не может обрабатывать ваш предикат, тогда она не может обрабатывать ваш предикат. (Вы всегда можете вызвать ToList() если вам нужно оценить до дальнейшей фильтрации, которая не может быть переведена).

  2. изменение запроса может вызвать проблемы с производительностью, но, скорее всего, предоставит столь необходимую функциональность. Кроме того, проблемы производительности, связанные с неоптимальным запросом LINQ, вероятно, будут значительно менее вредными, чем проблемы производительности понесенные потянув гораздо больше записей, чем вам нужно, чтобы избежать разоблачения IQueryable или путем систематической фильтрации любой логики доступа к данным через раздутые уровни абстракций, которые на самом деле ничего не делают (первая угроза более значительна). В общем, это не будет проблемой, потому что большинство ведущих поставщиков LINQ оптимизируют логику запросов в процессе перевода.

если вы хотите скрыть вашу логику запроса от переднего конца, то не пытайтесь создание универсального хранилища. Инкапсулируйте запросы с помощью реальных бизнес-методов. Теперь я могу ошибаться, но я предполагаю, что ваше использование шаблона репозитория вдохновлено дизайном, управляемым доменом. Если это так, то причина использования репозитория заключается в том, чтобы позволить вам создать домен с сохраняемостью с основным фокусом на модели домена. Однако использование такого универсального репозитория не делает намного больше, чем изменение семантики из Create Read Update Delete to Find Add Remove Save. Там там нет никаких бизнес-знаний.

рассмотрим значимость (и удобство использования)

interface IPersonRepository 
{ 
     Person GetById(int id);
     IEnumerable<Person> FindByName(string firstName, string lastName);
}

в отличие от

interface IRepository<T> { 
     IEnumerable<T> FindBy(Query<T> query);
}

кроме того, можете ли вы на самом деле указать на преимущество использования IRepository<T> вообще (в отличие от IQueryable<T>)?

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

*еще одна заметка о ресурсах, которые советуют не использовать IQueryable<T>, стоит ли смотреть на дату их публикации. Было время, когда доступность поставщиков LINQ была довольно ограничена (до ранних EF и LINQ-to-SQL). В то время разоблачение IQueryable<T> повлечет за собой несовместимость с некоторыми из более популярных заменителей Microsoft ORM (LINQ-to-NHibernate уже давно реализован). На данный момент поддержка LINQ практически повсеместно в серьезных библиотеках ORM .NET


В отличие от царствующего ответа выше, четкой точкой оппозиции было бы тестирование. Возвращение простого generic IQueryable - это утечка абстракции для любого клиента,который имеет доступ к вызову. Поэтому для метода репозитория нет ограничений, нечего утверждать в модульном тесте.

использование IQueryable почти наверняка зависит от контекста самого приложения, и архитектура. Например, если вы работаете над личным проектом вы сами можете спокойно взять на себя ответственность за обладание этой неограниченной властью, не проклиная никого, кроме себя. Однако, если вы работаете над приложением для компании с кодом, идущим в производство, есть, безусловно, больше заинтересованных сторон, чем вы. В этом случае вы можете работать на уровне доступа к данным и пытаться кодировать против использования кода другим разработчиком. Теперь вам нужно контролировать, что входит и выходит, чтобы они не нарушали ваш код, и их не сломаешь. Это стимул, а модульное тестирование-принуждение.

С этим сказано, не просто использовать IQueryable<T>. использовать то, что имеет смысл для вашего приложения.