Смутно прохождение аргументов 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())