LINQ:动态排序多列

4

我有一个IQueryable的查询,想要动态地对其进行排序,可以根据许多列进行排序(升序或降序)。我编写了以下通用函数:

    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是我简单的枚举,有两个值:升序和降序。

然后我在循环中调用此函数,针对用户请求排序的每一列。 但是我注意到它失败了,因为它总是按照最后使用的列进行排序,忽略了其他列。

然后我发现IOrderedQueryable接口上有一个'ThenBy'方法,有效的用法是:

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

但是我该如何使它通用呢?我尝试测试查询是否为IOrderedQueryable,但似乎始终为true,即使它是最简单的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进行子排序(正如您所期望的那样)。 - Ian Jacobs
查询执行被延迟,当你编写 .OrderBy 时,直到第一次迭代查询集时才会发生任何事情。 - PawelRoman
5个回答

2
你只需要检查查询是否已经排序:
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);
    }
}

2
这个不起作用。就像我说的,查询始终是IOrderedQueryable类型,即使它只是从x in y select x的最简单形式。 - PawelRoman

1

那么只把第一个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);
    }
}

我使用了这种方法,对我很有效,但需要注意的是最后我必须将orderedData重新赋值给datadata = orderedData我还使用了Linq中的*.Skip(1)扩展和foreach*循环:foreach (var sortExpr in columnsToOrderBy.Skip(1)) - Tamas

0

动态多订单扩展:

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 提供程序能够翻译这个。


0

全凭猜测,但你可以像这样做吗?

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

除了语法错误之外,这个想法是在OrderBy()中不影响任何东西,然后在.ThenBy()方法调用中进行真正的工作。


很好的 hack,可以完成工作,但还是一个 hack。很难相信 linq 的作者没有考虑到这种情况。使用 Python/Django 完成这样的事情是如此的轻松... - PawelRoman
虽然它不影响顺序,但它仍然对性能有影响吗? - schnellboot

0

我会编写一个包装器,并在内部使用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);
}

提示:我没有测试过这段代码


如果你测试这段代码,你会发现IQueryable<T>总是IOrderedQueryable<T>,所以ThenBy函数总是被调用,而OrderBy永远不会被调用。这与我在写这篇文章之前尝试实现它的方式类似,并发现了这个惊喜。 - PawelRoman

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