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

问题描述

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

  var people = People 
.Where(p => ; p.Cars
.Any(c => c.Colour == p.FavouriteColour));

我相信我正确构造了查询,但是 ExpressionExpander.VisitMethodCall (..)方法在尝试使用它时会抛出以下异常:

在现实世界中代码,使用实体框架和实际的 IQueryable< T> ,我经常得到:

我已经构建了一个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& 20)
};

var queryableTuples = tuples.AsQueryable();

//对于这个例子,我想检查这些字符串中哪些比其附带的数字更长。
//我想要构建的表达式需要使用项(int)的一个值来构造表达式。
//基本上只想构造这个:
// .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(){

//我传递的东西(例如Tuple)包含:
// *我需要构造表达式的值(例如,'最小长度')
// *我需要调用表达式的值(例如字符串)

return tuple => BuildExpressionToCheckStringLength(tuple.Item2 / * length * /)。Invoke(tuple.Item1 / * string * /);

}

如果我做的事情显然是错误的,我会真正感谢向正确的方向微推!谢谢。






编辑:我知道以下内容可以正常工作:

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

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

但是,我试图将比较与参数的位置分开,因为比较可能是复杂的,我想重新使用它为许多不同的查询(每个具有两个参数不同的位置)。还有一个参数(在示例中,最小长度)实际上将通过另一个表达式计算。





$ b $对不起,我刚刚意识到,尝试反对我的示例代码时,一些答案将会起作用,因为我的例子只是伪装成一个 IQueryable< T> 仍然是列表< T> 下面。我首先使用LinqKit的原因是因为EntityFramework DbContext中的实际 IQueryable< T> 将调用Linq-to-SQL,因此必须能够由Linq-to-SQL本身解析。 LinqKit通过将所有内容扩展到表达式来实现。






解决方案!感谢,我想我已经意识到我错了什么。



如果一个值来自于查询中的某个地方(即不是一个已知在之前的值),那么您必须将该引用/表达式/变量构建到表达式中。 p>

在我的原始示例中,我试图传递从表达式中取得的'minLength'值,并将其传递给一个方法。该方法调用无法在之前完成,因为它使用表达式中的值,并且无法在表达式中完成,因为您无法在表达式中构建表达式。



那么,如何解决这个问题?我选择写我的表达式,以便可以使用附加参数调用它们。虽然这样做的缺点是这些参数不再是命名的,但我可能会得到一个表达式< Func< int,int,int,int,bool>> 或者下线的东西。

  //新签名。 
public Expression< Func< string,int,bool>> BuildExpressionToCheckStringLength(){

//现在需要两个参数。
return(str,minLength)=> str.Length>的minLength;

}

public Expression< Func< Tuple< string,int>,bool>> BuildExpressionToCheckTuple(){

//构建前面的表达式。
var expression = BuildExpressionToCheckStringLength();

//使用这两个值调用表达式。
return tuple => expression.Invoke(tuple.Item1 / * string * /,tuple.Item2 / * length * /);

}


解决方案

所以你试图做的(从一个函数中转换一个参数,它返回另一个函数,它将一个参数 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(){
//我传递的东西(例如:Tuple)包含:
// *我需要构造表达式的值(例如'最小长度')
// *我需要调用表达式的值(例如,字符串)

//将构建器置于变量中,以便生成的表达式将为
// visible分析表达式的工具。
var builder = ExpressionToCheckStringLengthBuilder();

return tuple => builder.Invoke(tuple.Item2 / * length * /)。Invoke(tuple.Item1 / * string * /);
}


I'm trying to dynamically construct an expression similar to the one below, where I can use the same comparison function, but where the values being compared can be passed in, since the value is passed from a property 'higher-up' in the query.

var people = People
    .Where(p => p.Cars
        .Any(c => c.Colour == p.FavouriteColour));

I believe I've constructed the query correctly, but the ExpressionExpander.VisitMethodCall(..) method throws the following exception when I try to use it:

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

这篇关于尝试在子集合表达式中使用父属性作为参数; LinqKit抛出“无法将MethodCallExpressionN转换为LambdaExpression”的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-30 18:25