Почему отдельно созданный предикат Func не преобразуется в SQL с Entity Framework?

#sql #linq #entity-framework #ef-code-first #generic-function

#sql #linq #entity-framework #ef-code-first #generic-function

Вопрос:

У меня есть контекст EF Code First Db, который я использую для запроса к базе данных. Я заметил некоторые проблемы с производительностью при передаче запросов как Func<Product, bool> s из моего агрегированного репозитория, и при дальнейшем исследовании оказалось, что запросы не преобразуются в запросы SQL.

После еще небольшого изучения я обнаружил следующее.

 var results = _context.Products
            .Where(p => p.ProductCode.Contains("AAA"))
            .Where(p => p.CategoryId == 1)
            .ToList();
  

Это работает точно так, как ожидалось. Он генерирует некоторый параметризованный SQL с предложением Where .

==================================================================

 var results2 = _context.Products
            .Where(p => p.ProductCode.Contains("AAA") amp;amp; p.CategoryId == 1)
            .ToList();
  

Это также работает, как и ожидалось. Он генерирует тот же sql, что и выше

==================================================================

 Func<Product, bool> pred = (p => p.ProductCode.Contains("AAA") amp;amp; p.CategoryId == 1);

var results3 = _context.Products.Where(pred).ToList();
  

Это не работает. Он не генерирует предложение where в SQL, он возвращает все, а затем фильтрует это в коде.

Ответ №1:

Потому что для перевода в SQL он должен быть an Expression<...> , а не a Func<...> .

Это делается автоматически для вас компилятором, и поскольку перегрузки классов Linq-to-SQL принимают выражения, а не делегаты, компилятор автоматически преобразует ваш код (который выглядит как лямбда или анонимный метод) в объект expression и передает его.

Однако, если вы позаботитесь о создании функции самостоятельно, компилятор не сможет этого сделать, а Linq-to-SQL не использует анонимные методы, он принимает только выражения.

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

Комментарии:

1. Спасибо Лассе, да, у меня было это озарение после отправки Q.

Ответ №2:

Как только я опубликовал это, ReSharper помог ответить на мой вопрос, показав мне сигнатуру метода перегрузки для метода Where() расширения.

Для этого требуется оба Func<T, bool> и Expression<Func<T, bool>> . Если вы объявляете свои предикаты извне, вы должны использовать вариант выражения, поскольку первый не преобразуется в sql.

Ответ №3:

Вот почему запрос считывает всю таблицу целиком.

Когда вместо an Func используется a Expression , компилятор выбирает методы on System.Linq.Enumerable — вместо System.Linq.Queryable . Enumerable Методы перебирают исходную коллекцию (иногда лениво), в то время Queryable как методы создают дерево выражений.

Поскольку вызов Where не является частью дерева выражений, генератор sql не видит его во время преобразования запроса.