我正在尝试动态构建一个类似下面的表达式,其中我可以使用相同的比较函数,但要比较的值可以传递,因为该值是从查询中的“高级”属性传递的。
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();
然而,我试图将比较与参数的位置分开,因为比较可能很复杂,我想重用它来执行许多不同的查询(每个查询具有不同的两个参数位置)。还打算通过另一个表达式计算其中一个参数(在示例中为“最小长度”)。
编辑:抱歉,我刚意识到一些答案只能针对我的示例代码进行尝试,因为我的示例只是伪装成了一个>,但实际上是一个List 。我之所以首先使用LinqKit,是因为来自EntityFramework DbContext的实际>会调用Linq-to-SQL,因此必须能够被Linq-to-SQL本身解析。 LinqKit通过将所有内容扩展为表达式来实现这一点。
解决方案!感谢下面Jean的答案,我认为我已经意识到自己的错误所在。
如果值来自查询中的某个地方(即不是预先知道的值),则必须将对其的引用/表达式/变量构建到表达式中。
在我的原始示例中,我试图传递从表达式中取出的'minLength'值并将其传递给方法。由于它使用表达式中的值,因此无法在之前进行方法调用,并且由于您无法在表达式中构建表达式,因此无法在表达式中进行方法调用。
那么,如何解决这个问题?我选择编写我的表达式,以便可以使用附加参数调用它们。虽然这样做的缺点是参数不再是“命名的”,并且我最终可能会得到一个Expression >之类的东西。
// 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<...
? - sgmooreAsExpandable()
是LinqKit的主要神奇功能,所有内容都需要成为表达式,这样后续就可以直接编译为SQL。 - Ben JenkinsonInvoke
是一个扩展方法吗?来自 LinqKit? - Jean HominalInvoke
是一个辅助方法,它扩展到.Compile().Invoke(arg1, arg2..)
,以满足编译器。在运行时,AsExpandable()
使用一个自定义的ExpressionVisitor
叫做LinqKit.ExpressionExpander
来删除所有对Invoke
的调用,并用表达式树代替已编译的函数。(我不完全理解它的工作原理,所以来这里了,但我想大致就是这样。)LinqKit 的主页在这里: http://www.albahari.com/nutshell/linqkit.aspx 或者您也可以在 Github 上查看源代码: https://github.com/scottksmith95/LINQKit - Ben Jenkinson