使用Reflection调用OrderBy(System.Linq.Enumerable)

时间:2014-02-13 12:44:59

标签: c# linq

我正在使用开源系统,并且已经被要求根据术语对一组实体进行排序。

需要首先按照完全匹配的顺序排序,然后“startswith”匹配,然后“包含”匹配。

我想出来的lambda表达式是:

var results = products.OrderBy(p => p.Name.StartsWith(searchTerm) ? Convert.ToString((char)0) : p.Name);

目前,预先编写的系统的工作方式是调用带有反射的lambda表达式实例的OrderBy方法,以便对结果进行排序。

为了尽量减少花在这项任务上的时间,我想遵守这个惯例。我试图创建一个lambda表达式的实例并将其作为参数传递给MethodInfo.Invoke(),但因为它有一个变量,它会抛出异常。

该系统的排序方法的代码是:

        private IOrderedQueryable<ReferenceProduct> OrderProducts(IQueryable<ReferenceProduct> filteredProducts, String sortExpression, String sortDirection, out String checkedSortExpression, out String checkedSortedDescending)
    {
        ProductListManager.SortExpression correspondingSortExpression = this.SortExpressions.OrderByDescending(se => String.Equals(se.Code, sortExpression, StringComparison.OrdinalIgnoreCase))
                                                                                            .DefaultIfEmpty(new SortExpression<Guid> { Code = String.Empty, Expression = rp => rp.ProductId })
                                                                                            .FirstOrDefault();
        checkedSortExpression = correspondingSortExpression.Code;
        checkedSortedDescending = String.Equals("desc", sortDirection, StringComparison.OrdinalIgnoreCase) ? "desc" : "asc";
        MethodInfo orderMethod = (checkedSortedDescending.Equals("desc", StringComparison.OrdinalIgnoreCase) ? (Queryable.OrderByDescending) : new Func<IQueryable<Object>, Expression<Func<Object, Object>>, IOrderedQueryable<Object>>(Queryable.OrderBy)).Method.GetGenericMethodDefinition().MakeGenericMethod(typeof(ReferenceProduct), correspondingSortExpression.Expression.ReturnType);
        IOrderedQueryable<ReferenceProduct> orderedProducts = orderMethod.Invoke(null, new Object[] { filteredProducts, correspondingSortExpression.Expression }) as IOrderedQueryable<ReferenceProduct>;
        return orderedProducts;
    }

有没有人有任何想法?

提前致谢。

赖安

2 个答案:

答案 0 :(得分:3)

这与我可以做到的一样短,使用dynamic来做一些模糊的方法解决方案(一个邪恶的黑客,但在这里非常值得 - 手动添加没有任何用处)。我没有依赖具有searchTerm的“测试类”的未定义范围,而是将其提升到最高级别 - 但您可能需要不同的东西(因此我在评论范围方面持续存在问题) searchTerm):

class ReferenceProduct {
    public string Name { get; set; }

    static readonly Dictionary<string, Func<string, LambdaExpression>> knownSortFactories =
    new Dictionary<string, Func<string,LambdaExpression>> {
        {"name", searchTerm =>(Expression<Func<ReferenceProduct, string>>)(p => p.Name.StartsWith(searchTerm) ? Convert.ToString((char)0) : p.Name) }
        // ... more here
    };

    public static IOrderedQueryable<ReferenceProduct> OrderProducts(IQueryable<ReferenceProduct> filteredProducts, string sortExpression, string sortDirection, string queryTerm)
    {
        Func<string, LambdaExpression> factory;
        if (!knownSortFactories.TryGetValue(sortExpression, out factory))
            throw new InvalidOperationException("Unknown sort expression: " + sortExpression);
        dynamic lambda = factory(queryTerm); // evil happens here ;p
        switch(sortDirection)
        {
            case "asc":
                return Queryable.OrderBy(filteredProducts, lambda);
            case "desc":
                return Queryable.OrderByDescending(filteredProducts, lambda);
            default:
                throw new InvalidOperationException("Unknown sort direction: " + sortDirection);
        }
    }
}

使用示例用法(此处使用LINQ-to-Objects作为外观):

static void Main()
{
    var source = new[] {
        new ReferenceProduct { Name = "def" },
        new ReferenceProduct { Name = "fooghi" },
        new ReferenceProduct { Name = "abc" }
    }.AsQueryable();
    var sorted = ReferenceProduct.OrderProducts(source, "name", "asc", "foo");
    var arr = sorted.ToArray(); 
    foreach(var item in arr) {
        Console.WriteLine(item.Name);
    }
}

输出:

fooghi
abc
def

答案 1 :(得分:1)

如果没有反射调用调用,您可以完全实现的目标。

而不是

var orderedProducts = orderMethod.Invoke(null, new Object[] { filteredProducts, correspondingSortExpression.Expression }) as IOrderedQueryable<ReferenceProduct>;

只需使用查询提供程序:

var orderedProducts = filteresProducts.Provider.CreateQuery<ReferenceProduct>(Expression.Call(null, orderMethod, Expression.Constant(correspondingSortExpression.Expression)));