Я добавляю ответ для новичков, потому что эти ответы казались мне над головой, пока я не понял, насколько это просто. Иногда вы ожидаете, что это сложно, что делает вас неспособным «обернуть голову вокруг этого».
Мне не нужно было понимать разницу, пока я не наткнулся на действительно досадную «ошибку», пытающуюся использовать LINQ-to-SQL в общем:
public IEnumerable<T> Get(Func<T, bool> conditionLambda){
using(var db = new DbContext()){
return db.Set<T>.Where(conditionLambda);
}
}
Это прекрасно работало, пока я не начал получать исключения OutofMemoryException для больших наборов данных. Установка точек останова внутри лямбды заставила меня понять, что он перебирает каждую строку в моей таблице один за другим в поисках совпадений с моим состоянием лямбды. Это поставило меня в тупик на некоторое время, потому что, черт возьми, он обрабатывает мою таблицу данных как гигантский IEnumerable вместо того, чтобы выполнять LINQ-to-SQL, как положено? То же самое делал и мой коллега LINQ-MongoDb.
Исправление было просто превратить Func<T, bool>
в Expression<Func<T, bool>>
, поэтому я гуглил, почему ему нужен Expression
вместо Func
, заканчивающийся здесь.
Выражение просто превращает делегата в данные о себе. Так что a => a + 1
становится чем-то вроде: «С левой стороны есть int a
. С правой стороны вы добавляете 1 к нему». Вот и все. Теперь вы можете идти домой. Это, очевидно, более структурировано, чем это, но на самом деле это все дерево выражений - ничего, что можно было бы обернуть.
Понимая это, становится понятно, почему для LINQ-to-SQL требуется Expression
, а Func
- недостаточно. Func
не влечет за собой способ проникнуть в себя, понять, как перевести его в запрос SQL / MongoDb / other. Вы не можете видеть, делает ли он сложение, умножение или вычитание. Все, что вы можете сделать, это запустить его. Expression
, с другой стороны, позволяет заглянуть внутрь делегата и увидеть все, что он хочет сделать. Это позволяет вам перевести делегата во что угодно, например, в SQL-запрос. Func
не сработало, потому что мой DbContext был закрыт для содержания лямбда-выражения. Из-за этого он не мог превратить лямбда-выражение в SQL; тем не менее, он сделал следующую лучшую вещь и повторил это условие по каждой строке в моей таблице.
Редактировать: излагая мое последнее предложение по просьбе Джона Питера:
IQueryable расширяет IEnumerable, поэтому методы IEnumerable, такие как Where()
, получают перегрузки, которые принимают Expression
. Когда вы передаете Expression
этому, вы сохраняете IQueryable в результате, но когда вы передаете Func
, вы отступаете от базового IEnumerable и в результате вы получаете IEnumerable. Другими словами, не замечая, что вы превратили свой набор данных в список для повторения, а не для запроса. Трудно заметить разницу, пока вы действительно не заглянете под капотом на подписи.