Я пытаюсь динамически построить выражение, подобное приведенному ниже, где я могу использовать ту же функцию сравнения, но где сравниваемые значения могут быть переданы, поскольку значение передается из свойства «выше» в запрос.
var people = People
.Where(p => p.Cars
.Any(c => c.Colour == p.FavouriteColour));
Я считаю, что я правильно построил запрос, но метод ExpressionExpander.VisitMethodCall(..)
выдает следующее исключение, когда я пытаюсь его использовать:
«Невозможно привести объект типа 'System.Linq.Expressions.InstanceMethodCallExpressionN' к типу 'System.Linq.Expressions.LambdaExpression'»
В реальном коде, используя Entity Framework и фактический IQueryable<T>
, я часто получаю:
«Не удалось преобразовать объект типа 'System.Linq.Expressions.MethodCallExpressionN' к типу 'System.Linq.Expressions.LambdaExpression'».
Я создал удобный для LinqPad пример моей проблемы, настолько простой, насколько я мог это сделать.
void Main()
{
var tuples = new List<Tuple<String, int>>() {
new Tuple<String, int>("Hello", 4),
new Tuple<String, int>("World", 2),
new Tuple<String, int>("Cheese", 20)
};
var queryableTuples = tuples.AsQueryable();
// For this example, I want to check which of these strings are longer than their accompanying number.
// The expression I want to build needs to use one of the values of the item (the int) in order to construct the expression.
// Basically just want to construct this:
// .Where (x => x.Item1.Length > x.Item2)
var expressionToCheckTuple = BuildExpressionToCheckTuple();
var result = queryableTuples
.AsExpandable()
.Where (t => expressionToCheckTuple.Invoke(t))
.ToList();
}
public Expression<Func<string, bool>> BuildExpressionToCheckStringLength(int minLength) {
return str => str.Length > minLength;
}
public Expression<Func<Tuple<string, int>, bool>> BuildExpressionToCheckTuple() {
// I'm passed something (eg. Tuple) that contains:
// * a value that I need to construct the expression (eg. the 'min length')
// * the value that I will need to invoke the expression (eg. the string)
return tuple => BuildExpressionToCheckStringLength(tuple.Item2 /* the length */).Invoke(tuple.Item1 /* string */);
}
Если я делаю что-то явно не так, я был бы очень признателен, если бы меня подтолкнули в правильном направлении! Спасибо.
Изменить: я знаю, что сработает следующее:
Expression<Func<Tuple<string, int>, bool>> expr = x => x.Item1.Length > x.Item2;
var result = queryableTuples
.AsExpandable()
.Where (t => expr.Invoke(t))
.ToList();
Однако я пытаюсь отделить сравнение от местоположения параметров, поскольку сравнение может быть сложным, и я хотел бы повторно использовать его для множества разных запросов (каждый с разными местоположениями для двух параметров). Также предполагается, что один из параметров (в примере «минимальная длина») будет фактически вычисляться через другое выражение.
Изменить: извините, я только что понял, что некоторые ответы будут работать при попытке против моего примера кода, поскольку мой пример просто маскируется под IQueryable<T>
, но все еще List<T>
внизу. Причина, по которой я использую LinqKit в первую очередь, заключается в том, что фактический IQueryable<T>
из EntityFramework DbContext будет вызывать Linq-to-SQL и, следовательно, должен иметь возможность анализировать сам Linq-to-SQL. LinqKit обеспечивает это, расширяя все до выражений.
Решение! Благодаря ответу Джин ниже, я думаю, что понял, куда иду неправильный.
Если значение получено откуда-то в запросе (т. Е. не значение, которое известно заранее.), Тогда вы должны встроить ссылку / выражение / переменную на него в выражение.
В моем исходном примере я пытался передать значение minLength, взятое из выражения, и передать его методу. Этот вызов метода нельзя было выполнить заранее, поскольку он использовал значение из выражения, и это невозможно было сделать внутри выражения, поскольку вы не можете построить выражение внутри выражения.
Итак, как это обойти? Я решил написать свои выражения так, чтобы их можно было вызывать с дополнительными параметрами. Хотя у этого есть обратная сторона: параметры больше не имеют «именований», и я могу получить Expression<Func<int, int, int, int, bool>>
или что-то в этом роде.
// New signature.
public Expression<Func<string, int, bool>> BuildExpressionToCheckStringLength() {
// Now takes two parameters.
return (str, minLength) => str.Length > minLength;
}
public Expression<Func<Tuple<string, int>, bool>> BuildExpressionToCheckTuple() {
// Construct the expression before-hand.
var expression = BuildExpressionToCheckStringLength();
// Invoke the expression using both values.
return tuple => expression.Invoke(tuple.Item1 /* string */, tuple.Item2 /* the length */);
}
AsExpandable()
иExpression<Func<...
, а не простоFunc<...
? - person sgmoore   schedule 13.05.2014AsExpandable()
- это главная магия LinqKit, все должно быть выражением, чтобы оно могло быть скомпилированным непосредственно в SQL позже. - person Ben Jenkinson   schedule 13.05.2014Invoke
Вы используете метод расширения? Из LinqKit? - person Jean Hominal   schedule 13.05.2014Invoke
- это вспомогательный метод, который расширяется до.Compile().Invoke(arg1, arg2..)
, что удовлетворяет компилятор. Во время выполненияAsExpandable()
использует пользовательскийExpressionVisitor
, называемыйLinqKit.ExpressionExpander
, чтобы удалить все вызовыInvoke
и заменить их деревом выражений, а не скомпилированной функцией. (Я не совсем понимаю, как это работает, поэтому я здесь, но я думаю, что это примерно так.) Домашняя страница LinqKit находится здесь: albahari.com/nutshell/linqkit.aspx или вы можете просмотреть исходный код на Github: github.com/scottksmith95/LINQKit - person Ben Jenkinson   schedule 13.05.2014