本文介绍了尝试使用parent属性为子集合表达参数; LinqKit抛出"无法投MethodCallExpressionN到LambdaExpression"的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图动态构造类似下面的人,在那里我可以使用相同的比较函数的表达式,但如果被比较中可以通过,因为该值是从属性更高了传递的值在查询中。



变种人=人民
。凡(p => ; p.Cars
。任何(C => c.Colour == p.FavouriteColour));



我相信我已经构造正确的查询,但在 ExpressionExpander.VisitMethodCall (..)方法抛出以下异常,当我尝试使用它:



In real-world code, using Entity Framework and actual IQueryable<T>, I often get:

I've constructed a LinqPad-friendly example of my problem, as simple as I could make it.

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 */);

}

If I'm doing something obviously wrong, I'd really appreciate a nudge in the right direction! Thanks.


Edit: I know that the following would work:

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

var result = queryableTuples
    .AsExpandable()
    .Where (t => expr.Invoke(t))
    .ToList();

However, I'm trying to separate the comparison from the location of the parameters, since the comparison could be complex and I would like to re-use it for many different queries (each with different locations for the two parameters). It is also intended that one of the parameters (in the example, the 'min length') would actually be calculated via another expression.


Edit: Sorry, I've just realised that some answers will work when attempted against my example code since my example is merely masquerading as an IQueryable<T> but is still a List<T> underneath. The reason I'm using LinqKit in the first place is because an actual IQueryable<T> from an EntityFramework DbContext will invoke Linq-to-SQL and so must be able to be parsed by Linq-to-SQL itself. LinqKit enables this by expanding everything to expressions.


Solution! Thanks to Jean's answer below, I think I've realised where I'm going wrong.

If a value has come from somewhere in the query (i.e. not a value that is known before-hand.) then you must build the reference/expression/variable to it into the expression.

In my original example, I was trying to pass the 'minLength' value taken from within the expression and pass it to a method. That method call could not be done before-hand, since it used a value from the expression, and it could not be done within the expression, since you can't build an expression within an expression.

So, how to get around this? I chose to write my expressions so that they can be invoked with the additional parameters. Though this has the downside that the parameters are no longer 'named' and I could end up with an Expression<Func<int, int, int, int, bool>> or something down the line.

// 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 */);

}
解决方案

OK, so what you are trying to do (the transformation from a function that takes a single argument, that returns another function that takes a single argument f(x)(y) into a function that takes two arguments f(x, y)) is known as uncurrying. Look it up! :)

Now, the issue that you have in your code is that, in the expression returned by BuildExpressionToCheckTuple, there is a method call to BuildExpressionToCheckStringLength, which is not resolved. And you cannot resolve it because it takes an argument that is embedded in the tuple parameter.

The solution is, instead of using a method call, to use a lambda expression that will be equivalent to that method call.

That is:

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 */);
}

这篇关于尝试使用parent属性为子集合表达参数; LinqKit抛出&QUOT;无法投MethodCallExpressionN到LambdaExpression&QUOT;的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-30 18:24