Linq处理可变数量的OrderBy
我需要在Linq(to Entity)语句中支持可变数量的Orderby术语。 也就是说,我的函数将接受数据应该在其上排序的属性列表。 属性可以具有升序或降序排序。 处理构造Linq查询的最佳方法是什么?
谢谢!
你应该能够沿着这些方向做点什么:
public IEnumerable DoSomething(params Expression>[] properties) { var query = // create LINQ query that returns IQueryable 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个属性的情况。
继Jay的答案之后 ,这可以成为一个很好的扩展方法:
public static class EnumerableExtensions { public static IEnumerable OrderByMany (this IEnumerable enumerable, params Expression>[] 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
UPDATE
您可以添加OrderByMany
方法的另一个重载以支持SortOrder,尽管它很快就会变得笨拙。 就个人而言,我只是去寻找语法
var query = from p in people order by Name, Age descending;
但是,为了记录,至少在C#4中,我将使用枚举和元组完成重载。
public enum SortOrder { Ascending, Descending }
和额外的过载:
public static IEnumerable OrderByMany (this IEnumerable enumerable, params Tuple>,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>, SortOrder>(x => x.Age, SortOrder.Descending), new Tuple>, SortOrder>(x => x.Name, SortOrder.Ascending))) { Console.WriteLine("{0} Age={1}", per.Name, per.Age); }
要按任意属性排序,您需要构建一个表达式树以传递给OrderBy
。
要按任意数量的属性排序,您需要在循环中调用ThenBy
。
我喜欢Jamiec的想法,但我讨厌使用元组,因为语法很难看。 因此,我构建了一个封装了Tuple的小类,并为Item1和Item2属性公开了具有更好变量名的getter。
另请注意,我使用了升序的默认排序顺序,因此如果要按降序排序,则只需指定SortOrder。
public class SortExpression { private Tuple>, SortOrder> tuple; public SortExpression( Expression> expression, SortOrder order =SortOrder.Ascending ) { tuple = new Tuple>, SortOrder>(expression, order); } public Expression> Expression { get { return tuple.Item1; } } public SortOrder Order { get { return tuple.Item2; } } }
在我的特定应用程序中,我有一个存储库基类,它接受IQueryable并将其转换为ObservableCollection。 在该方法中,我使用SortExpression类:
public ObservableCollection GetCollection(params SortExpression [] sortExpressions) { var list = new ObservableCollection (); 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(x => x.FirstName), new SortExpression (x => x.LastName));