Смутно прохождение аргументов Expression vs. Func
У меня возникли проблемы с пониманием различий между тем, как работают выражения и функции Funcs.
Эта проблема возникла, когда кто-то изменил подпись метода из:
public static List<Thing> ThingList(Func<Thing, bool> aWhere)
Для
public static List<Thing> ThingList(Expression<Func<Thing, bool>> aWhere)
Кто нарушил мой код. Старый код вызова (который работал) выглядел следующим образом:
...
object y = new object();
Func<Thing, bool> whereFunc = (p) => p == y;
things = ThingManager.ThingList(whereFunc);
Новый код (который не работает) выглядит следующим образом:
...
object x = new object();
Expression<Func<Thing, bool>> whereExpr = (p) => p == x;
things = ThingManager.ThingList(whereExpr);
Это не выполняется в ThingList (...) в строке, используя выражение:
var query = (from t in context.Things.Where(aWhere)
...
С ошибкой во время выполнения:
Unable to create a constant value of type 'System.Object'. Only primitive types ('such as Int32, String, and Guid') are supported in this context.
Этот пример надуман, но я предполагаю, что он имеет какое-то отношение к локальной переменной объекта x, которая не была правильно скопирована в выражение.
Может кто-нибудь объяснить, как справиться с этой ситуацией в целом, и почему работает Func
, но Expression
нет?
Ответы
Ответ 1
Причиной изменения почти наверняка было "толкать" оценку вашего предиката в основной магазин, который поддерживает ваш context
. Вместо того, чтобы переносить все Things
в память и затем использовать Func<Thing,bool>
, чтобы решить, какие из них сохранить, автор измененного API решил использовать IQueryable
и для этого нужен Expression<Func<Thing,bool>>
.
Вы правильно указали происхождение ошибки: в отличие от предикатов в памяти, IQueryable
не может использовать объекты, которые он не знает, например. произвольные экземпляры object
.
Что вам нужно сделать, так это изменить выражение, чтобы не ссылаться на объекты типов данных, которые не поддерживаются вашим целевым хранилищем данных (я предполагаю, что это выражение в конечном итоге входит в структуру Entity Framework или Linq2Sql). Например, вместо того, чтобы говорить
object x = new object();
Expression<Func<Thing, bool>> whereExpr = (p) => p == x;
things = ThingManager.ThingList(whereExpr);
вы должны сказать
Thing x = new Thing {id = 123};
Expression<Func<Thing, bool>> whereExpr = (p) => p.id == x.id;
things = ThingManager.ThingList(whereExpr);
(ваш резервный магазин почти наверняка понимает целые числа)
Ответ 2
Разница между выражением и Func лучше описана в ответах здесь: Разница между выражением <Func → и Func < gt;
Быстрое обходное решение, чтобы снова выполнить эту работу, состояло в том, чтобы скомпилировать выражение в Func.
var query = (from t in context.Things.Where(aWhere.Compile())