Как уже обсуждалось в Domain-Driven Design: Repository, реализация шаблона Repository фактически превращается в статический класс (даже, если у него нет слова static) с большим количеством методов. Кроме этого возникает рад вопросов к реализации шаблона Repository, на которые нет простых ответов.
Какой репозиторий использовать, если метод выборки работает с несколькими сущностями?
Если нам надо выбрать Account по Project, то мы пойдем за этим методом в AccountRepository или ProjectRepository? А если выборка идет по 3-м сущностям, то где искать нужный метод?
Что делать, если разные репозитории должны использовать закрытые методы друг друга?
Можно из одного репозитория вызывать другой, но это превратит код в паутину. Можно один репозиторий унаследовать от другого, но это приведет к огромным God-object'ам.
Может использовать вообще один репозиторий на весь проект?
Это еще хуже, потому что это будет нетестируемый god-object с безграничной ответственностью.
Может репозиторий - это просто построитель запросов?
Тогда логика построения запроса и его выполнения будет разбросана по коду. Например, можно создать часть запроса в сервисе, добавить в него условий в контроллере, а выполнить запрос в модели. Логика в запросах неизбежно начнет дублироваться. К тому же такие «размазанные» запросы будет сложно тестировать.
Надо ли дублировать методы в сервисе и репозитории?
Это вопрос о том, стоит ли давать контроллерам доступ напрямую к репозиториям или только через сервисы. Если нет, то тогда вызовы репозитория приходится оборачивать в сервисах.
Вопросы можно продолжать, но главное то, что шаблон Repository утратил свою актуальность. Либо репозитории разрастаются, либо логика запросов расплывается по всем частям проекта.
Делим Repository на Query
Самым простым и рабочим решением является разделение репозиториев на небольшие объект-запросы. В проекте эта реализация тесно переплетается с концепции CQRS. Мы применяем ее уже около года, поэтому решение хорошо обкатано и проверено на рабочих проектах.
Суть в том, что у нас есть набор объектов, каждый из который делает одну операцию чтения. Только чтения, не записи. Одна логическая операция чтения равна одному объекту запроса.
Например, нам надо найти Account по адресу электронной почты. Создаем класс FindAccountByEmailQuery.
public class FindAccountByEmailQuery { private readonly ILinqProvider linqProvider; private string email; public FindAccountByEmailQuery(ILinqProvider linqProvider, string email) { this.linqProvider = linqProvider; this.email = email; } public Account Execute() { return linq.Query() .Where(x => x.Email == email) .SingleOrDefault(); } }
Объект делает ровно столько, сколько написано в его названии, тестировать очень просто, логики очень мало. Может многократно использоваться в проекте.
Инфраструктура
Со временем у нас будет довольно много маленьких объектов типа *Query, поэтому будет удобно сделать доступ к ним через фабрику.
public interface IQueryFactory { [CanBeNull] Account FindAccountByEmail(string email); // ... } public class QueryFactory : IQueryFactory { private readonly ILinqProvider linqProvider; public QueryFactory(ILinqProvider linqProvider) { this.linqProvider = linqProvider; } public Account FindAccountByEmail(string email) { return new FindAccountByEmailQuery(linqProvider, email).Execute(); } }
Теперь интерфейс IQueryFactory можно инжектировать в другие объекты. Например, выберем данные в методе AccountController:
public class AccountController : Controller { private readonly IQueryFactory queryFactory; private readonly IUnitOfWorkFactory unitOfWorkFactory; public AccountController(IQueryFactory queryFactory, IUnitOfWorkFactory unitOfWorkFactory) { this.queryFactory = queryFactory; this.unitOfWorkFactory = unitOfWorkFactory; } public ActionResult LogOn(string email) { using(unitOfWorkFactory.Create()) { Account account = queryFactory.FindAccountByEmail(email); // ... return View(); } } }
Структура проекта, где используются Query, может выглядеть так:

Осталось реализовать инжекцию зависимостей, интерфейсы ILinqProvider и IUnitOfWorkFactory. Пример для реализации доступа к данным через NHibernate можно посмотреть на Google Code — nhibernate2-unitofwork.
Бестелесный IQueryFactory
Интерфейс IQueryFactory получается довольно громоздким, поэтому его лучше заменить на интерфейс с одним методом. См. статью Заменяем QueryFactory на бестелесный IQueryFactory.
Заключение
Если ваши репозитории уже являются «всемогущими» объектам, которые невозможно тестировать, то самое время для рефакторинга. Если ваши репозитории еще только начали свой жизненный путь, то на ранней стадии вы сможете решить будущие проблемы. Главное, что теперь понятно, куда двигаться.
Посмотрите ссылки, приведенные ниже, чтобы еще глубже понять проблему работы с шаблоном Repository и концепцию решения.
Ссылки
Repository is the new Singleton
DDD : Command Query Separation as an Architectural Concept
Query Objects vs Methods on a Repository