Должен ли репозиторий возвращать IEnumerable <T>, IQueryable <T>или List <T>? - PullRequest
15 голосов
/ 12 ноября 2011

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

Какой тип объекта лучше всего подходит для репозитория?IEnumerable, IQueryable или List?

Используемые мной технологии:

  • Кэширование фабрики приложений Azure

  • Entity Framework 4.1

  • Возможно, Windows Server AppFabric

Ответы [ 4 ]

9 голосов
/ 12 ноября 2011

Это зависит от того, хотите ли вы, чтобы какие-либо будущие запросы выполнялись для объекта, и должны ли они быть в памяти или нет:

  • Если должны быть будущие запросы, и БД должна выполнитьwork return IQueryable.
  • Если должны быть будущие запросы, и это должно быть сделано в памяти, верните IEnumerable.
  • Если дальнейших запросов не будет и все данные должны бытьпрочитайте, верните IList, ICollection и т. д.
5 голосов
/ 17 ноября 2011

Я бы сказал, создайте свой DAL, используя IQueryable, и передайте его, убедитесь, что ваш объект предполагает, что время жизни - это запрос.Таким образом, вы получите выгоду от отложенного выполнения, но подвержены риску неэффективных запросов к базе данных.

Затем убедитесь, что вы тестируете производительность своего приложения (или хотя бы частей, которые с наибольшей вероятностью получат трафик) иувидеть шаблоны доступа к данным.Создайте специализированные методы в вашем DAL для получения полностью материализованных объектов и сделайте эти запросы как предварительно скомпилированные запросы.

образец интерфейса репозитория будет выглядеть как

  public interface IDataContext
  {
        void Add<T>(T entity) where T : BaseEntity;
        void Delete<T>(T entity) where T : BaseEntity;
        IQueryable<T> Find<T>(Expression<Func<T, bool>> where) where T : BaseEntity;
   }

, где BaseEntity - базовый класспохоже, что для всех наших классов этот класс не сопоставлен ни с одной таблицей в db

public abstract class BaseEntity
{
        public int Id { get; set; }
        public DateTime CreateDateTime { get; set; }
        public string CreateUser { get; set; }
        public DateTime ModDateTime { get; set; }
        public string ModUser { get; set; }
        public byte[] RowVersion { get; set; }
}

Expression<Func<T, bool>> будет передавать все выражение в ваш репозиторий вместо просто Func, поскольку EF работает с выражением длягенерировать sql-запрос, типичное использование будет

ICollection<WFGroup> wgGroups = this.dataContext.Find<WFGroup>((w) => true).ToList();

, где WFGroup - это класс, производный от BaseEntity, я обычно использую ленивую загрузку и прокси и не отсоединяю / присоединяю объекты к контексту.

4 голосов
/ 12 ноября 2011

Насколько вероятно, что вам когда-нибудь понадобится вернуть пользовательскую реализацию IEnumerable (не коллекцию) из вашего DAL?(Чтобы ответить на этот вопрос, посмотрите на свои предыдущие проекты и посчитайте, сколько из них или из yield return s у вас есть.)

Если ответ «не очень», я просто верну ICollection или даже массивы (если вы хотите предотвратить непреднамеренное изменение результатов запроса.) В крайнем случае, если вам когда-либо понадобится изменить запрос на «потоковые» результаты с пользовательским IEnumerable, вы всегда можете использовать старый методпозвоните новому и материализуйте результаты, чтобы сохранить совместимость со старыми клиентами.

1 голос
/ 27 сентября 2017

Существует очень хорошая недавняя статья здесь , которая охватывает это, а именно под заголовком «Репозитории, которые возвращают IQueryable».Вот что он говорит:

Одна из причин, по которой мы используем шаблон хранилища, заключается в инкапсуляции толстых запросов.Эти запросы затрудняют чтение, понимание и тестирование действий в контроллерах ASP.NET MVC.Кроме того, по мере роста приложения увеличивается вероятность повторения толстого запроса в нескольких местах.С помощью шаблона репозитория мы инкапсулируем эти запросы в классы репозитория.Результат - более тонкие, чистые, более удобные в обслуживании и более простые в тестировании действия.Рассмотрим этот пример:

var orders = context.Orders
    .Include(o => o.Details)
    .ThenInclude(d => d.Product)
    .Where(o => o.CustomerId == 1234);

Здесь мы напрямую используем DbContext без шаблона репозитория.Когда ваши методы репозитория возвращают IQueryable, кто-то другой собирается получить этот IQueryable и составить запрос поверх него.Вот результат:

var orders = repository.GetOrders()
    .Include(o => o.Details)
    .ThenInclude(d => d.Product)
    .Where(o => o.CustomerId == 1234);

Вы видите разницу между этими двумя фрагментами кода?Разница только в первой строке.В первом примере мы используем context.Orders , во втором мы используем repository.GetOrders () .Итак, какую проблему решает этот репозиторий?Ничего!

Ваши репозитории должны возвращать доменные объекты.Таким образом, метод GetOrders () должен возвращать IEnumerable .При этом второй пример можно переписать так:

var orders = repository.GetOrders (1234);

Видите разницу?

КакВ результате я добавил в мою команду следующее соглашение о кодировании:

Для методов класса репозитория никогда не возвращайте объект IQueryable .Всегда сначала перечисляйте или конвертируйте его (например, ToArray , ToList , AsEnumerable ).

Причина в том, что IQueryable позволит вызывающей стороне использовать это и в конечном итоге изменить запрос SQL, который выполняется в базе данных.Это может быть опасно с точки зрения производительности БД, но это больше касается SoC.Вызывающая сторона не заботится об источнике данных;он просто хочет данные.

...