尝试在父集合表达式中使用父属性作为参数; LinqKit抛出"无法将MethodCallExpressionN强制转换为LambdaExpression"

Ben*_*son 5 c# linq expression linq-expressions linqkit

我正在尝试动态构造一个类似于下面的表达式,我可以使用相同的比较函数,但是可以传入被比较的值,因为值是从属性'higher-up'传递的查询.

var people = People
    .Where(p => p.Cars
        .Any(c => c.Colour == p.FavouriteColour));
Run Code Online (Sandbox Code Playgroud)

我相信我已正确构造了查询,但是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 */);

}
Run Code Online (Sandbox Code Playgroud)

如果我做了一些明显错误的事情,我真的很感激在正确的方向上轻推!谢谢.


编辑:我知道以下内容可行:

Expression<Func<Tuple<string, int>, bool>> expr = x => x.Item1.Length > x.Item2;

var result = queryableTuples
    .AsExpandable()
    .Where (t => expr.Invoke(t))
    .ToList();
Run Code Online (Sandbox Code Playgroud)

不过,我想从参数上的位置比较独立,因为比较可能是复杂的,我想重新使用它为许多不同的查询(每个具有两个参数的不同位置).还意图通过另一个表达式实际计算其中一个参数(在该示例中,"最小长度").


编辑:对不起,我刚刚意识到,当我尝试对付我的示例代码时,某些答案会起作用,因为我的例子只是伪装成一个IQueryable<T>但仍然是一个List<T>底层.我首先使用LinqKit的原因是因为IQueryable<T>EntityFramework DbContext中的实际内容将调用Linq-to-SQL,因此必须能够由Linq-to-SQL本身进行解析.LinqKit通过将所有内容扩展到表达式来实现这一点.


解!感谢Jean的回答,我想我已经意识到我哪里出错了.

如果某个值来自查询中的某个位置(即不是前面已知的值),那么必须将其中的reference/expression/variable构建到表达式中.

在我的原始示例中,我试图传递从表达式中获取的'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 */);

}
Run Code Online (Sandbox Code Playgroud)

Jea*_*nal 0

好的,所以您想要做的事情(从一个采用单个参数的函数,返回另一个采用单个参数的函数f(x)(y)到采用两个参数的函数的转换f(x, y))被称为非柯里化。查一下!:)

现在,您的代码中存在的问题是,在 返回的表达式中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 */);
}
Run Code Online (Sandbox Code Playgroud)