Linq处理可变数量的OrderBy

时间:2011-01-24 17:10:30

标签: c# linq linq-to-entities

我需要在Linq(to Entity)语句中支持可变数量的Orderby术语。也就是说,我的函数将接受数据应该在其上排序的属性列表。属性可以具有升序或降序排序。处理构造Linq查询的最佳方法是什么?

谢谢!

4 个答案:

答案 0 :(得分:9)

你应该能够沿着这些方向做点什么:

public IEnumerable<MyType> DoSomething(params Expression<Func<MyType,object>>[] properties)
 {
     var query = // create LINQ query that returns IQueryable<MyType>
     query = query.OrderBy(properties.First());

     foreach (var property in properties.Skip(1))
     {
         query = query.ThenBy(property);
     }
 }

 …

 var results = DoSomething(() => x.Age, () => x.Height, () => x.LastName);

您需要处理指定少于2个属性的情况。

答案 1 :(得分:3)

Jay's answer开始,这可以成为一个很好的扩展方法:

public static class EnumerableExtensions
{
    public static IEnumerable<T> OrderByMany<T>(this IEnumerable<T> enumerable, 
        params Expression<Func<T, object>>[] expressions)
    {
        if (expressions.Length == 1)
            return enumerable.OrderBy(expressions[0].Compile());

        var query = enumerable.OrderBy(expressions[0].Compile());
        for (int i = 1; i < expressions.Length;i++)
        {
            query = query.ThenBy(expressions[i].Compile());
        }
        return query;

    }
}

给定测试对象,用法变得非常简单:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

这是可能的:

var people = new Person[]
                    {
                        new Person() {Name = "John", Age = 40},
                        new Person() {Name = "John", Age = 20},
                        new Person() {Name = "Agnes", Age = 11}
                    };

foreach(var per in  people.OrderByMany(x => x.Name, x => x.Age))
{
    Console.WriteLine("{0} Age={1}",per.Name,per.Age);
}

输出:

Agnes Age=11
John Age=20
John Age=40

<强>更新

您可以添加OrderByMany方法的另一个重载来支持SortOrder,尽管它很快就会变得笨拙。就个人而言,我只是去寻找语法

var query = from p 
            in people
            order by Name, Age descending;

然而,至于记录,至少在C#4中,我会使用枚举和放大器完成重载。元组。

public enum SortOrder
{
    Ascending, 
    Descending
}

和额外的过载:

public static IEnumerable<T> OrderByMany<T>(this IEnumerable<T> enumerable,
    params Tuple<Expression<Func<T, object>>,SortOrder>[] expressions)
{

    var query = (expressions[0].Item2 == SortOrder.Ascending)
                    ? enumerable.OrderBy(expressions[0].Item1.Compile())
                    : enumerable.OrderByDescending(expressions[0].Item1.Compile());

    for (int i = 1; i < expressions.Length; i++)
    {
        query = expressions[i].Item2 == SortOrder.Ascending
                    ? query.ThenBy(expressions[i].Item1.Compile())
                    : query.ThenByDescending(expressions[i].Item1.Compile());
    }
    return query;

}

用法变得笨拙且难以阅读:

foreach (var per in people.OrderByMany(
                    new Tuple<Expression<Func<Person, object>>, SortOrder>(x => x.Age, SortOrder.Descending), 
                    new Tuple<Expression<Func<Person, object>>, SortOrder>(x => x.Name, SortOrder.Ascending)))
{
    Console.WriteLine("{0} Age={1}", per.Name, per.Age);
}

答案 2 :(得分:2)

要按任意属性排序,您需要构建一个表达式树以传递给OrderBy

要按任意数量的属性排序,您需要在循环中调用ThenBy

答案 3 :(得分:0)

我喜欢Jamiec的想法,但我讨厌使用元组因为语法很难看。因此,我构建了一个封装了Tuple的小类,并为Item1和Item2属性公开了具有更好变量名的getter。

另请注意,我使用了升序的默认排序顺序,因此如果要按降序排序,则只需指定SortOrder。

public class SortExpression<T>
{
    private Tuple<Expression<Func<T, object>>, SortOrder> tuple;        

    public SortExpression( Expression<Func<T, object>> expression, SortOrder order =SortOrder.Ascending )
    {
        tuple = new Tuple<Expression<Func<T,object>>, SortOrder>(expression, order);
    }

    public Expression<Func<T, object>> Expression {
        get { return tuple.Item1; }
    }

    public SortOrder Order {
        get { return tuple.Item2; }
    }
}

在我的特定应用程序中,我有一个存储库基类,它接受IQueryable并将其转换为ObservableCollection。在该方法中,我使用SortExpression类:

public ObservableCollection<T> GetCollection(params SortExpression<T>[] sortExpressions) {
    var list = new ObservableCollection<T>();
    var query = FindAll();

    if (!sortExpressions.Any()) {
        query.ToList().ForEach(list.Add);
        return list;
    }

    var ordered = (sortExpressions[0].Order == SortOrder.Ascending)
        ? query.OrderBy(sortExpressions[0].Expression.Compile())
        : query.OrderByDescending(sortExpressions[0].Expression.Compile());

    for (var i = 1; i < sortExpressions.Length; i++) {
        ordered = sortExpressions[i].Order == SortOrder.Ascending
            ? ordered.ThenBy(sortExpressions[i].Expression.Compile())
            : ordered.ThenByDescending(sortExpressions[i].Expression.Compile());
    }

    ordered.ToList().ForEach(list.Add);
    return list;
}        

以下是使用方法:

var repository = new ContactRepository(UnitOfWork);
return repository.GetCollection(
                    new SortExpression<Contact>(x => x.FirstName),
                    new SortExpression<Contact>(x => x.LastName));