【问题标题】:Invoking OrderBy (System.Linq.Enumerable) with Reflection使用反射调用 OrderBy (System.Linq.Enumerable)
【发布时间】:2014-02-13 21:21:51
【问题描述】:

我正在使用一个开源系统,并被要求按与术语的相关性对一组实体进行排序。

该集合需要首先以完全匹配排序,然后是“startswith”匹配,然后是“contains”匹配。

我想出的 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;
    }

有人有什么想法吗?

提前致谢。

瑞恩

【问题讨论】:

  • 您想要(或需要)使用反射的原因是什么?
  • 这是针对的开源系统通过对其他事物的反射来实现的。不清楚为什么会这样写。我不愿意改变它。
  • 在您的编辑中,我注意到它使用的是IQueryable&lt;T&gt;。 IEnumerable&lt;T&gt; 和 IQueryable&lt;T&gt; 之间存在巨大的差异。如果是IQueryable&lt;T&gt;,那么您一开始就不应该使用委托;你应该使用Expressions....你能澄清一下吗?
  • 你能举例说明sortExpression 可能包含的内容,以便我理解这个场景吗?或者ProductListManager.SortExpression 是什么?
  • 注意:我可以向您推荐这个 (stackoverflow.com/a/233505/23354),了解在 IQueryable&lt;T&gt; 上进行动态排序的一般方法。创建一个稍微复杂一点的 lambda 并不是什么大困难,但我根本不明白所有部分的来源。

标签: c# linq


【解决方案1】:

这是我能做到的最简短的,使用dynamic 来做一些晦涩的方法解析(一个邪恶的黑客,但在这里非常值得 - 手动做没有任何用处)。我没有依赖具有searchTerm 的“测试类”的未定义范围,而是将其提升到顶层 - 但您可能需要一些不同的东西(因此我在 cmets 中继续提出关于@987654323 范围的问题@):

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

【讨论】:

    【解决方案2】:

    您想要实现的目标完全可以在没有反射调用调用的情况下实现。

    代替

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

    【讨论】:

    • 顺便说一下,这也正是 IQueryable 的 OrderBy/OrderByDescending 扩展方法要做的事情。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-08
    相关资源
    最近更新 更多