IEnumerable vs IQueryable для бизнес-логики или типов возврата DAL

Я знаю, что эти вопросы заданы раньше, я начну с перечисления некоторых из них (тех, которые я читал до сих пор):

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

В первую очередь меня интересует вопрос IEnumerable vs IQueryable и, более конкретно, связь между DAL и его потребителями.

Я нашел разные мнения относительно двух интерфейсов, которые были замечательными. Тем не менее, я обеспокоен последствиями возврата DAL IQueryable. Насколько я понимаю, IQueryable предлагает/подразумевает, что под капотом есть Linq Provider. Это касается номера один - что, если DAL внезапно потребует данные из источника, не связанного с Linq? Следующее будет работать, но это скорее хак?

public static IQueryable<Product> GetAll()
{
    // this function used to use a L2S context or similar to return data 
    // from a database, however, now it uses a non linq provider

    // simulate the non linq provider...
    List<Product> results = new List<Product> { new Product() };
    return results.AsQueryable();
}

Итак, я могу использовать расширение AsQueryable(), хотя я не согласен с тем, чтобы точно знать, что это делает? Я всегда предполагаю, что IQueryables являются основными деревьями выражений, которые мы можем добавлять по мере необходимости, пока мы не сможем выполнить наш запрос и получить результаты.

Я мог бы исправить это, изменив возвращаемый тип функции на IEnumerable. Затем я могу вернуть IQueryable из функции, потому что он наследует IEnumerable, и я получаю отложенную загрузку. Я теряю способность добавлять к выражению запроса:

var results = SomeClass.GetAll().Where(x => x.ProductTypeId == 5);

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

Как другие люди обойдутся?

  • Предоставить больше функций в DAL - GetAllByProductType, GetAllByStartDate,... и т.д.
  • Предоставить перегрузку, которая принимает предикаты? то есть.

    public static IEnumerable<Product> GetAll(Predicate<Product> predicate)
    {
    
        List<Product> results = new List<Product> { new Product() };
        return results.Where(x => predicate(x));
    }
    

Одна последняя часть (Извините, я знаю, очень длинный вопрос!).

Я нашел IEnumerable наиболее рекомендуемым во всех вопросах, которые я проверил, но как насчет требования отложенных нагрузок для доступа к datacontext? Как я понимаю, если ваша функция возвращает IEnumerable, но вы возвращаете IQueryable, IQueryable полагается на базовый datacontext. Поскольку результат на этом этапе фактически является выражением, и ничего не было принесено в память, вы не можете гарантировать, что потребитель DAL/функция будет выполнять запрос или когда. Так я должен держать экземпляр контекста, что результаты были получены из доступных каким-то образом? Это как/почему шаблон Unit of Work вступает в игру?

Сводка вопросов для ясности (выполнил поиск "?"...):

  • Если вы используете IQueryable как возвращаемый тип, слишком ли вы слишком тесно связываете свою пользовательскую логику/бизнес-логику с поставщиками Linq?
  • Является ли использование расширения AsQueryable() хорошей идеей, если вам вдруг нужно вернуть данные из источника, не связанного с Linq?
  • У кого-нибудь есть хорошая ссылка, описывающая, как, например, работает преобразование стандартного списка в AsQueryable, что он на самом деле делает?
  • Как вы обрабатываете дополнительные требования фильтрации, поставляемые бизнес-логикой, к вашему DAL?
  • Кажется, что отложенная загрузка IEnumerable и IQueryable подлежит поддержанию базового провайдера, следует ли использовать шаблон Unit of Work или что-то еще для этого?

Большое спасибо!

Ответ 1

  • ну, вы не строго связаны с каким-либо конкретным провайдером, а как повторное формулирование этого: вы не можете легко протестировать код, поскольку у каждого провайдера есть разные поддерживаемые функции (что означает: то, что работает для одного, может не быть работа для другого - даже что-то вроде .Single())
  • Я так не думаю, если у вас возникнет вопрос о постоянно меняющемся провайдере - см. выше
  • он просто предоставляет украшенную оболочку, которая использует .Compile() для любых лямбда, и вместо этого использует LINQ-to-Objects. Примечание. LINQ-to-Objects имеет большую поддержку, чем любой другой поставщик, поэтому это не будет проблемой, за исключением того, что это означает, что любые "макеты" с использованием этого подхода действительно не проверяют ваш фактический код вообще и в основном бессмысленны ( IMO)
  • Да, хитрый - см. ниже
  • Да, хитрый - см. ниже

Лично я бы предпочел здесь хорошо определенные API, которые принимают известные параметры и возвращают загруженные List<T> или IList<T> (или подобные) результаты; это дает вам проверяемый/макетируемый API и не оставляет вас во власти отложенного исполнения (закрытое адское соединение и т.д.). Это также означает, что любые различия между поставщиками обрабатываются внутренне для реализации вашего уровня данных. Он также значительно приближается для вызова сценариев, таких как веб-службы и т.д.

Короче говоря; учитывая выбор между IEnumerable<T> и IQueryable<T>, я не выбираю ни того, чтобы вместо этого использовать IList<T> или List<T>. Если мне нужна дополнительная фильтрация, то либо:

  • Я добавлю это к существующему API через параметры и сделаю фильтрацию внутри своего уровня данных
  • Я соглашусь с тем, что негабаритные данные возвращаются, которые затем мне нужно отфильтровать у вызывающего