5

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

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 和 actual 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();

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


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


解决方案!感谢Jean在下面的回答,我想我已经意识到我哪里出错了。

如果某个值来自查询中的某个位置(即不是事先已知的值),那么您必须将它的引用/表达式/变量构建到表达式中。

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

}
4

2 回答 2

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();
        }
于 2014-05-13T12:21:05.987 回答
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 */);
}
于 2014-05-14T08:46:18.317 回答