【问题标题】:LINQ: Sorting on many columns dynamicallyLINQ:动态排序多列
【发布时间】:2010-07-23 15:34:20
【问题描述】:

我有一个 IQueryable 查询并希望对其动态应用排序,排序可以在许多列上(asc 或 desc)。我编写了以下通用函数:

    private IQueryable<T> ApplySorting<T,U>(IQueryable<T> query, Expression<Func<T, U>> predicate, SortOrder order)
    {
        if (order == SortOrder.Ascending)
        {
            {
                return query.OrderBy<T, U>(predicate);
            }
        }
        else
        {
            {
                return query.OrderByDescending<T, U>(predicate);
            }
        }
    }

SortOrder 是我的简单枚举,有 2 个值:升序和降序

然后我循环调用这个函数,为用户请求排序的每一列。但是我注意到它失败了,因为它总是按使用的最后一列排序,而忽略其他列。

然后我发现 IOrderedQueryable 上有一个“ThenBy”方法,所以有效的用法是:

var q = db.MyType.OrderBy(x=>x.Col1).ThenBy(y=>y.Col2); //etc.

但是我怎样才能使它通用呢?我试图测试查询是否为 IOrderedQueryable 但它似乎总是正确的,即使它是最简单的 var q = from x in db.MyType select x

我不知道为什么要这样设计。有什么问题:

var q = db.MyType.OrderBy(x=>x.Col1).OrderBy(y=>y.Col2); //etc.

非常直观

【问题讨论】:

  • 我认为他们不让 .OrderBy(..Col1..).OrderBy(..Col2..) 飞是因为他们不是知道预期最终结果是否的好方法是按 Col1 排序,然后按 Col2 重新排序整个列表;或 Col1 然后是 Col2 的子排序(如您所愿)
  • 查询执行被延迟,当您编写 .OrderBy 时,在您第一次遍历查询集之前什么都不会发生。

标签: c# linq linq-to-entities


【解决方案1】:

您只需要检查查询是否已订购:

private IQueryable<T> ApplySorting<T,U>(IQueryable<T> query, Expression<Func<T, U>> predicate, SortOrder order)
{
    var ordered = query as IOrderedQueryable<T>;
    if (order == SortOrder.Ascending)
    {
        if (ordered != null)
            return ordered.ThenBy(predicate);
        return query.OrderBy(predicate);
    }
    else
    {
        if (ordered != null)
            return ordered.ThenByDescending(predicate);
        return query.OrderByDescending(predicate);
    }
}

【讨论】:

  • 这不起作用。就像我说的,查询 ALWAYS 的类型是 IOrderedQueryable,如果它是最简单的事件,则 from x in y select x;
【解决方案2】:

完全猜测,但你能做这样的事情吗?

query.OrderBy(x => 1).ThenBy<T,U>(predicate)

除了任何语法错误,想法是做一个不影响任何事情的 OrderBy(),然后在 .ThenBy() 方法调用中做真正的工作

【讨论】:

  • 不错的 hack,它应该可以完成这项工作,但它仍然只是一个 hack。很难相信 linq 作者没有想到这种情况。使用 python/django 做这样的事情是如此微不足道...
  • 虽然不影响订单,但对性能还有影响吗?
【解决方案3】:

我会编写一个包装器并在内部使用 linq 扩展方法。

var resultList =   presentList
        .MyOrderBy(x => x.Something)
        .MyOrderBY(y => y.SomethingElse)
        .MyOrderByDesc(z => z.AnotherThing)

public IQueryable<T> MyOrderBy(IQueryable<T> prevList, Expression<Func<T, U>> predicate) {

       return (prevList is IOrderedQueryable<T>)
            ? query.ThenBy(predicate)
            : query.OrderBy(predicate);
}

public IQueryable<T> MyOrderByDesc(IQueryable<T> prevList, Expression<Func<T, U>> predicate) {

       return (prevList is IOrderedQueryable<T>)
            ? query.ThenByDescending(predicate)
            : query.OrderByDescending(predicate);
}

PS:我没有测试代码

【讨论】:

  • 如果您测试这段代码,您会发现 IQueryable 始终是 IOrderedQueryable,因此将始终调用 ThenBy 函数而永远不会调用 OrderBy。这类似于我在写这里并发现这个惊喜之前尝试实现它的方式。
【解决方案4】:

让第一个OrderBy 静态,然后总是ThenBy 怎么样?

OrderColumn[] columnsToOrderby = getColumnsToOrderby();
IQueryable<T> data = getData();

if(!columnToOrderBy.Any()) { }
else
{
    OrderColumn firstColumn = columnsToOrderBy[0];
    IOrderedEnumerable<T> orderedData =
        firstColumn.Ascending
        ? data.OrderBy(predicate)
        : data.OrderByDescending(predicate);

    for (int i = 1; i < columnsToOrderBy.Length; i++)
    {
        OrderColumn column = columnsToOrderBy[i];
        orderedData =
            column.Ascending
            ? orderedData.ThenBy(predicate)
            : orderedData.ThenByDescending(predicate);
    }
}

【讨论】:

    【解决方案5】:

    动态多阶扩展:

    public static class DynamicExtentions
    {
        public static IEnumerable<T> DynamicOrder<T>(this IEnumerable<T> data, string[] orderings) where T : class
        {
            var orderedData = data.OrderBy(x => x.GetPropertyDynamic(orderings.First()));
            foreach (var nextOrder in orderings.Skip(1))
            {
                orderedData = orderedData.ThenBy(x => x.GetPropertyDynamic(nextOrder));
            }
            return orderedData;
        }
    
        public static object GetPropertyDynamic<Tobj>(this Tobj self, string propertyName) where Tobj : class
        {
            var param = Expression.Parameter(typeof(Tobj), "value");
            var getter = Expression.Property(param, propertyName);
            var boxer = Expression.TypeAs(getter, typeof(object));
            var getPropValue = Expression.Lambda<Func<Tobj, object>>(boxer, param).Compile();            
            return getPropValue(self);
        }
    }
    

    例子:

    var q =(myItemsToSort.Order(["Col1","Col2"]);
    

    注意:不确定是否有任何IQueryable 提供者可以翻译此内容

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-25
      • 1970-01-01
      • 2010-10-08
      • 1970-01-01
      相关资源
      最近更新 更多