Проблема скомпилированного запроса LINQ to SQL (работает как некомпилированный запрос)
У меня есть методы расширения C# на IQueryable
например, FindNewCustomers()
а также FindCustomersRegisteredAfter(int year)
и так далее, которые я использую, чтобы "связать" вместе запрос для LINQ to SQL.
Теперь к моей проблеме: я хочу создавать скомпилированные запросы, например:
private static Func<MyDataContext, SearchInfo, IQueryable<Customer>> CQFindAll = CompiledQuery.Compile((MyDataContext dc, SearchInfo info) => dc.Contacts.Select(c => c).FindCustomersRegisteredAfter(info.RegYear) .OrderBy(info.OrderInfo) .Skip(info.SkipCount) .Take(info.PageSize));
FindCustomersRegisteredAfter(int year)
Метод является методом расширения, принимающимIQueryable
и возвращаю то же самое.OrderBy
Метод также является методом расширения (System.Linq.Dynamic), который создает динамическое выражение на основе строки (например, "FirstName ASC" будет сортировать поле FirstName по возрастанию).Skip
а такжеTake
являются встроенными методами.Выше (не как скомпилированный запрос, но обычный запрос) работает отлично. Как только я поместил его в скомпилированный запрос, я столкнулся со следующей ошибкой:
Метод 'System.Linq.IQueryable`1[Domain.Customer] FindCustomersRegisteredAfter[Customer](System.Linq.IQueryable`1[Domain.Customer], Int32)' не поддерживает перевод на SQL.Еще раз, это прекрасно работает, если запрос не скомпилирован, просто обычный запрос LINQ. Ошибка появляется только тогда, когда она находится внутри CompiledQuery.Compile().
Помогите??!
Редактировать: если я создаю запрос с помощью var query = (...) так же, как и внутри CompiledQuery.Compile, это сгенерированный SQL:
SELECT [t1].[Id], [t1].[FirstName], [t1].[LastName], [t1].[RegYear], [t1].[DeletedOn] FROM ( SELECT ROW_NUMBER() OVER (ORDER BY [t0].[LastName]) AS [ROW_NUMBER], [t0].[Id], [t0].[FirstName], [t0].[LastName], [t0].[RegYear], [t0].[DeletedOn] FROM [dbo].[Contacts] AS [t0] WHERE ([t0].[RegYear] > @p0) AND ([t0].[DeletedOn] IS NULL) ) AS [t1] WHERE [t1].[ROW_NUMBER] BETWEEN @p1 + 1 AND @p1 + @p2 ORDER BY [t1].[ROW_NUMBER]
Итак, вы видите, что SQL все отлично переводим, поэтому мне нужно только заполнить @p0, @p1 и @p2, чтобы это работало многократно! Что не так с CompiledQuery.Compile?!?
Обновление: я понимаю, что OrderBy не может работать (так как это не параметр @p). Я все еще пытаюсь понять, почему CompiledQuery.Compile не будет работать с моими методами расширения. Информация в интернете на эту тему практически отсутствует.
1 ответ
Я считаю, что скомпилированный запрос должен быть переведен в SQL, чего не может быть ваш метод расширения. Если вы профилируете SQL, созданный вашим "обычным" запросом, вы можете обнаружить, что он выбирает всю таблицу, чтобы он мог передать все строки в ваш метод расширения.
Вы бы лучше поместили свою логику фильтрации в запрос (как часть дерева выражений), чтобы ее можно было преобразовать в SQL и запустить на стороне сервера.
OrderBy также является проблемой из-за пропуска. Вы должны сделать это переводимым в SQL или LINQ должен будет вернуть все строки, чтобы отфильтровать их на стороне клиента.
Если вы не можете выразить их как выражения LINQ, рассмотрите возможность создания функций SQL на сервере и сопоставления их с вашим DataContext. LINQ сможет преобразовать их в вызовы функций T-SQL.
РЕДАКТИРОВАТЬ:
Полагаю, я предполагал, что ваши методы расширения не строили деревья выражений. Сожалею.
Рассмотрим эту ссылку, которая кажется похожей на вашу проблему. Он ссылается на другую ссылку, которая более подробно.
Похоже, что MethodCallExpression является проблемой.
Этот код немного длинен для публикации здесь, но, как и в экспандере tomasp.net, я посещаю каждое выражение в дереве выражений, и если узел является MethodCallExpression, который вызывает метод, который возвращает дерево выражений, я заменяю это MethodCallExpression на дерево выражений возвращается при вызове метода.
Таким образом, проблема заключается в том, что при компиляции запроса метод не выполняется, поэтому нет дерева выражений для преобразования в SQL.