我正在尝试动态构造一个类似于下面的表达式,我可以使用相同的比较函数,但是可以传入比较的值,因为值是从属性'up-up传递的'在查询中。
var people = People
.Where(p => p.Cars
.Any(c => c.Colour == p.FavouriteColour));
我相信我已经正确构造了查询,但是当我尝试使用它时,ExpressionExpander.VisitMethodCall(..)
方法会抛出以下异常:
“无法将类型为'System.Linq.Expressions.InstanceMethodCallExpressionN'的对象转换为'System.Linq.Expressions.LambdaExpression'”
在真实世界的代码中,使用实体框架和实际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();
但是,我试图将比较与参数的位置分开,因为比较可能很复杂,我想将它重新用于许多不同的查询(每个查询具有不同的位置)。它也意味着其中一个参数(在这个例子中,'min length')实际上是通过另一个表达式计算的。
编辑:抱歉,我刚刚意识到在尝试对付我的示例代码时会有一些答案,因为我的示例仅仅伪装成IQueryable<T>
,但仍然是List<T>
。我首先使用LinqKit的原因是因为EntityFramework DbContext中的实际IQueryable<T>
将调用Linq-to-SQL,因此必须能够由Linq-to-SQL本身进行解析。 LinqKit通过将所有内容扩展为表达式来实现此目的。
解决方案!感谢Jean's answer below,我想我已经意识到自己哪里出错了。
如果某个值来自查询中的某个位置(即不是之前已知的值。)那么您必须将表达式/表达式/变量构建到表达式中。 / p>
在我的原始示例中,我试图传递从表达式中获取的'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 */);
}
答案 0 :(得分:0)
所以你正在寻找这样的东西:
public static class Program
{
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public static IQueryable<T> WherePropertyEquals<T, TProperty>(
this IQueryable<T> src, Expression<Func<T, TProperty>> property, TProperty value)
{
var result = src.Where(e => property.Invoke(e).Equals(value));
return result;
}
public static IQueryable<T> WhereGreater<T, TProperty>(
this IQueryable<T> src, Expression<Func<T, TProperty>> property, TProperty value)
where TProperty : IComparable<TProperty>
{
var result = src.Where(e => property.Invoke(e).CompareTo(value) > 0);
return result;
}
public static IQueryable<T> WhereGreater<T, TProperty>(
this IQueryable<T> src, Expression<Func<T, TProperty>> left, Expression<Func<T, TProperty>> right)
where TProperty : IComparable<TProperty>
{
var result = src.Where(e => left.Invoke(e).CompareTo(right.Invoke(e)) > 0);
return result;
}
public static void Main()
{
var persons = new List<Person>()
{
new Person
{
FirstName = "Jhon",
LastName = "Smith"
},
new Person
{
FirstName = "Chuck",
LastName = "Norris"
},
new Person
{
FirstName = "Ben",
LastName = "Jenkinson"
},
new Person
{
FirstName = "Barack",
LastName = "Obama"
}
}
.AsQueryable()
.AsExpandable();
var chuck = persons.WherePropertyEquals(p => p.FirstName, "Chuck").First();
var ben = persons.WhereGreater(p => p.LastName.Length, 6).First();
var barack = persons.WhereGreater(p => p.FirstName.Length, p => p.LastName.Length).First();
}
答案 1 :(得分:0)
好的,那么你要做的是什么(从一个带有单个参数的函数转换,它返回另一个带有单个参数f(x)(y)
的函数到一个带有两个参数的函数f(x, y)
)被称为uncurrying。抬头看! :)
现在,您的代码中存在的问题是,在BuildExpressionToCheckTuple
返回的表达式中,有一个方法调用BuildExpressionToCheckStringLength
,未已解析。你无法解决它,因为它需要一个嵌入在元组参数中的参数。
解决方案是使用与该方法调用等效的lambda表达式,而不是使用方法调用。
那是:
public Expression<Func<int, Func<string, bool>>> ExpressionToCheckStringLengthBuilder() {
return minLength =>
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)
// Putting builder into a variable so that the resulting expression will be
// visible to tools that analyze the expression.
var builder = ExpressionToCheckStringLengthBuilder();
return tuple => builder.Invoke(tuple.Item2 /* the length */).Invoke(tuple.Item1 /* string */);
}