动态Lambda表达式调用

6

当我运行这段代码时,出现了以下异常:

类型为System.Int64的ParameterExpression不能用于类型为System.Object的委托参数。

我知道这与代码中的Expression.Lambda<func<object,bool>>部分有关。总的来说,我想将任何类型的ParameterExpression传递到这个方法中,并调用表达式。

public static IQueryable<T> OrderData<T>(IQueryable<T> data)
{
    try
    {
        Order order = Order.ASC;
        var result = Enum.TryParse<Order>(_gridSettings.SortOrder, true, out order);
        if (_gridSettings.IsSearch)
        {
            data = ExpressionSort(order, data, typeof(T).GetProperty(_gridSettings.SortColumn));
        }
        else
        {
            data = ExpressionSort(order, data, _defaultColumn);
        }
    }
    catch (Exception ex)
    {
        log.WriteLog(MethodBase.GetCurrentMethod(), LogLevel.FATAL, ex);
    }
    return data;
}

private static IQueryable<T> ExpressionSort<T>(Order order, IQueryable<T> data, PropertyInfo property)
{
    // Compose the expression tree that represents the parameter to the predicate.
    ParameterExpression paramExpression = Expression.Parameter(property.PropertyType, property.Name);
    IQueryable<T> queryableData = data.AsQueryable<T>();
    switch (order)
    {
        case Order.ASC:
            return ExecuteCall(paramExpression, paramExpression, queryableData, "OrderBy");
        case Order.DESC:
            return ExecuteCall(paramExpression, paramExpression, queryableData, "OrderByDescending");
    }
    return data;
}

private static IQueryable<T> ExecuteCall<T>(Expression expression, ParameterExpression paramExpression, IQueryable<T> queryableData, string linqMethod)
{
    MethodCallExpression callExpression = Expression.Call(
                               typeof(Queryable),
                               linqMethod,
                               new Type[] { queryableData.ElementType },
                               queryableData.Expression,
                               Expression.Lambda<Func<object, bool>>(expression, new ParameterExpression[] { paramExpression }));
    // Create an executable query from the expression tree.
    return queryableData.Provider.CreateQuery<T>(callExpression);
}

编辑: 我看到了类似问题的答案

“Expression of type 'System.Int32' cannot be used for return type 'System.Object'”,但我不知道如何将其应用到我的代码中。

编辑2: 主要问题是这一行Expression.Lambda<Func<object, bool>>(conversion, new ParameterExpression[] { paramExpression }));引发了异常。paramExpression包含Int64,但它期望得到一个对象。我不知道如何从我已经拥有的信息动态地告诉Func,或者是否可能实现这一点。

目标: 我想做类似于这样的事情:data.OrderBy(x=>x.DynamicProperty);


你正在使用 T<object, bool>,而你的参数是一个结构体... 你不能将结构体强制转换为对象。 - Fabio Luz
假设我们有一个类 public class MyClass { public int IntProperty { get; set; } public long LongProperty { get; set; } },在这种情况下,_gridSettings 的简单定义是什么? - Der Kommissar
_gridSettings.SortOrder 是一个字符串(升序或降序)_gridSettings.IsSearch 是一个布尔值 _gridSettings.SortColumn 是要排序的列的字符串 - imGreg
当我使用我的样本集运行它时,我没有遇到那个错误。你能提供你正在使用的完整示例吗? - Der Kommissar
让我们在聊天中继续这个讨论 - imGreg
显示剩余4条评论
3个回答

3

这就是你要求的内容,我认为...我已经测试过了,似乎能够正常工作。

// Caching of the reflection
private static readonly MethodInfo orderByMethod = GetOrderByMethod("OrderBy");
private static readonly MethodInfo orderByDescendingMethod = GetOrderByMethod("OrderByDescending");

private static IOrderedQueryable<TSource> ExpressionSort<TSource>(Order order, IQueryable<TSource> source, PropertyInfo property)
{
    // Compose the expression tree that represents the parameter to 
    // the predicate.

    // The expression you would use is source => source.Property,

    // The parameter of the lambda, source
    ParameterExpression sourceExpression = Expression.Parameter(typeof(TSource), "source");

    // Accessing the expression
    MemberExpression propertyExpression = Expression.Property(sourceExpression, property);

    // The full lambda expression. We don't need the 
    // Expression.Lambda<>, but still the keySelector will be an
    // Expression<Func<,>>, because Expression.Lambda does it 
    // authomatically. LambdaExpression is simply a superclass of 
    // all the Expression<Delegate>
    LambdaExpression keySelector = Expression.Lambda(propertyExpression, sourceExpression);

    // The OrderBy method we will be using, that we have cached
    // in some static fields
    MethodInfo method = order == Order.ASC ? orderByMethod : orderByDescendingMethod;

    // Adapted from Queryable.OrderBy (retrieved from the reference
    // source code), simply changed the way the OrderBy method is
    // retrieved to "method"
    return (IOrderedQueryable<TSource>)source.Provider.CreateQuery<TSource>(Expression.Call(null, method.MakeGenericMethod(new Type[]
    {
        typeof(TSource),
        property.PropertyType
    }), new Expression[]
    {
        source.Expression,
        Expression.Quote(keySelector)
    }));
}

private static MethodInfo GetOrderByMethod(string methodName)
{
    // Here I'm taking the long and more correct way to find OrderBy/
    // OrderByDescending: looking for a public static method with the
    // right name, with two generic arguments and that has the 
    // parameters related to those two generic arguments in a certain
    // way (they must be IQueryable<arg0> and Expression<Func<arg0,
    // arg1>>
    MethodInfo orderByMethod = (from x in typeof(Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public)
                                where x.Name == methodName
                                let generics = x.GetGenericArguments()
                                where generics.Length == 2
                                let parameters = x.GetParameters()
                                where parameters.Length == 2 &&
                                    parameters[0].ParameterType == typeof(IQueryable<>).MakeGenericType(generics[0]) &&
                                    parameters[1].ParameterType == typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(generics))
                                select x).Single();

    return orderByMethod;
}

请不要使用AsQueryable<>()。它不会像你想的那样工作,除了单元测试和非常特定的用例之外,它是完全无用的。

这正是我想要的。我没有意识到它有多么复杂。 - imGreg
此外,如果您能够发布说明为什么这种方法比其他方法更有效的信息,那就太好了。 - Der Kommissar
@EBrown 已经注释了所有内容。 - xanatos

1
你可以使用我的OrderByString扩展。 https://www.nuget.org/packages/OrderByString/ 它接受字符串作为排序参数。 排序参数字符串可以是逗号分隔的属性名称列表,例如“Prop1,Prop2”,也可以包括排序顺序,如“Prop1 DESC,Prop2 ASC”。
using OrderByExtensions;

public static IQueryable<T> OrderData<T>(IQueryable<T> data)
{
    try
    {
        Order order = Order.ASC;
        var result = Enum.TryParse<Order>(_gridSettings.SortOrder, true, out order);

        var sortColumn = _gridSettings.IsSearch ? _gridSettings.SortColumn : _defaultColumn;

        data = data.OrderBy(sortColumn + " " + _gridSettings.SortOrder.ToString());
    }
    catch (Exception ex)
    {
        log.WriteLog(MethodBase.GetCurrentMethod(), LogLevel.FATAL, ex);
    }
    return data;
}

或者

您可以使用以下的 GetExpressionForProperty 方法,该方法返回 OrderBy、OrderByDescending、ThenBy 或 ThenByDescending 所期望的排序表达式。

private static IQueryable<T> ExpressionSort<T>(Order order, IQueryable<T> data, PropertyInfo property)
{
    Expression<Func<T, object>> propertyExpression = GetExpressionForProperty<T>(property);

    return order == Order.DESC ? data.OrderByDescending(propertyExpression) : data.OrderBy(propertyExpression);
}

static Expression<Func<TSource, object>> GetExpressionForProperty<TSource>(PropertyInfo propertyInfo)
{
    var param = Expression.Parameter(typeof(TSource));

    return Expression.Lambda<Func<TSource, object>>(
        Expression.Convert(
            Expression.Property(param, propertyInfo),
            typeof(object)
        )
        , param);
}

0

我确实看到了那个答案,但我不知道如何将其应用到我的代码中。我不知道该在 Func<this part,bool> 中放什么。 - imGreg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接