Почему EF выбрасывает «NotSupportedException: метод« Первый »может использоваться только как конечная операция запроса» - PullRequest
7 голосов
/ 18 октября 2011
int[] ids1 = { 1, 2, 3 };
int[] ids2 = { 1, 5, 6 };

var result = from a in ids1
             where a == ids2.First()
             select a;  
foreach (var item in result) ; //ok


var employees = from c in context.Employees.
                where c.EmployeeID == ids1.First() 
                select c;   
foreach (var item in employees); // NotSupportedException

При попытке вызвать ids1.First в запросе Linq-to-Entities я получаю исключение System.NotSupportedException: метод «Первый» может использоваться только как конечная операция запроса.Попробуйте использовать метод FirstOrDefault в этом случае вместо .

a) Я не понимаю, почему First можно использовать только как окончательную операцию запроса, поскольку в нашем примере Firstвызывается на IEnumerable<> (ids1.First()), а не на IQueryable<>.Другими словами, First вызывается в запросе Linq-to-Objects, а не в запросе Linq-to-Entities ?!

b) В любом случае, почему First должна использоваться в качестве конечной операции запроса, в то время как FirstOrDefault не должна быть конечной операцией запроса?

Спасибо

ОТВЕТИТЬ:

Что касается разницы между First () и FirstOrDefault () - я не знаю.Вы пробовали, и это работает?

Да, это работает

Нет, First () вызывается в запросе LINQ to Entities.Ваше предложение where будет преобразовано в: Где (c => c.EmployeeID == ids1.First ())

a) Теперь я немного растерялся.Я понимаю, что ids1.First по существу вызывается в запросе Linq-to-Entities, но факт остается фактом: First вызывается в IEnumerable<>, и, как таковой, First вызывается в запросе Linq-to-Objects, и этот Linqзапрос to-Object в свою очередь вызывается в запросе Linq-To-entity - по крайней мере, я так понимаю ?!

Или вы подразумеваете, что First как-то вызывается IQeryable<>?

b) Я понимаю, что (c => c.EmployeeID == ids1.First()) будет преобразовано в дерево выражений, но почему нет ids1.First() выполнено до преобразования?

c) В любом случае, когда преобразование в дерево выражений произойдет, я предполагаю, что когда поставщик sql получает дерево выражений нашего предложения where и пытается преобразовать его в команду Sql, этот поставщик Sql не имеет«сила» для выполнения ids1.First, чтобы получить результат обратно (который он затем поместил бы в Sql-запрос), и, следовательно, исключение?!

ВТОРОЙ ОТВЕТ:

a) Я все еще не понимаю, почему ids1.First не выполняется до преобразования дерева выражений ?!А именно, в следующем предложении

Where(c => c.EmployeeID == 2+3) 

выражение 2+3 выполняется до того, как это предложение Где преобразуется в дерево выражений!И ids.First также является своего рода выражением, поэтому я ожидал бы похожего поведения?!

b) Извините за повторение, но меня действительно беспокоит, вызвано ли мое предположение - что First вызывается в Linqзапрос to-Objects, и этот запрос Linq-to-Object, в свою очередь, вызывается в запросе Linq-To-entity - это правильно?!

c) Возможно, я неправильно понял Ваш пост, но вы подразумеваете, что большинство другихОператоры Linq-to-Object можно вызывать для IEnumerable<> E, даже если в запросе Linq-to-Entities содержится E?

1 Ответ

8 голосов
/ 18 октября 2011

Нет, First() - это , вызываемый в запросе LINQ to Entities.Ваше предложение where будет преобразовано в:

Where(c => c.EmployeeID == ids1.First())

Это лямбда-выражение будет преобразовано в дерево выражений.

Конечно, довольно просто сделать это за пределами запрос:

int firstId = ids1.First();
var employees = from c in context.Employees
                where c.EmployeeID == firstId
                select c;

, который становится проще:

int firstId = ids1.First();
var employees = context.Employees.Where(c => c.EmployeeID == firstId);

Что касается разницы между First() и FirstOrDefault() - я не знаю.Вы пробовали это, и это работает?Возможно, это связано с тем, что First() будет вызывать исключение при вызове пустой последовательности, и такое поведение может быть трудно перевести по какой-то причине.

РЕДАКТИРОВАТЬ: Да, поставщик запросов может потенциально посмотреть на этот бит дерева выражений и разобраться с ним - но рано или поздно вам придется нарисовать линию, насколько умный поставщик запросовдолжен быть.Он уже проделал большую работу, и вам довольно легко выполнить работу здесь (согласно моему примеру выше) - так почему бы не сделать это?

Имейте в виду, что логически, First() выполняется для каждого элемента context.Employees - поэтому нужно только генерировать исключение для пустой коллекции, если существуют какие-либо строки - в противном случае вызов First() никогда не выполняется логически.Видите, это не совсем так просто, как вы могли бы ожидать :) В этом случае вы знаете, что являются элементами, поэтому вы можете безнаказанно вызывать First() - нопоставщик запросов не может.

РЕДАКТИРОВАТЬ: Ответить на второе редактирование ...

a) Выражение 2 + 3 является константой времени компиляции.Измените его на x + 2, и операция сложения будет частью дерева выражений.В частности, если вы измените значение x (или ids1 в вашем примере), которое изменит запрос - вы ничего не сможете сделать, чтобы изменить значение 2 + 3.

b) Это не такмне ясно, что ты имеешь в виду под этим.Дерево выражений, содержащееся в запросе EF, включает в себя вызов Enumerable.First, если вы это имеете в виду.

c) Поставщик запросов должен решить, какие именно деревья выражений поддерживать - и это относится к другимвызовы методов (например, int.Parse), а также методы LINQ to Objects.

...