OrderBy基于字段列表和Asc / Desc规则

我有以下带有OrderBy参数的List

 List fields = new List { "+created", "-approved", "+author" } 

这将导致以下Linq查询:

 IQueryable posts = _context.posts.AsQueryable(); posts = posts .OrderBy(x => x.Created) .ThenByDescending(x => x.Approved); .ThenBy(x => x.Author.Name); 

所以基本上规则是:

  1. 使用OrderBy的第一项和ThenBy的其他项。
  2. 当字段以字段开头时使用降序,而使用+时使用递增。

我的想法是有类似的东西:

 OrderExpression expression = posts .Add(x => x.Created, "created") .Add(x => x.Approved, "approved") .Add(x => x.Author.Name, "author"); 

因此,表达式将post属性/子属性与字段中的每个键相关联。 然后它将应用如下:

 posts = posts.OrderBy(expression, fields); 

因此, OrderBy扩展将遍历OrderExpression中的每个项目,并应用规则(1)和(2)来构建查询:

 posts = posts .OrderBy(x => x.Created) .ThenByDescending(x => x.Approved); .ThenBy(x => x.Author.Name); 

如何才能做到这一点?

这个答案是@YacoubMassad和我的共同努力。 有关详细信息,请查看单独的答案。 以下代码完美地工作,甚至可以毫无问题地转换为SQL(我在2008 R2上检查了这个问题的答案),因此所有排序都在服务器上完成(或者无论数据在哪里,它都适用于简单的列表当然也是如此)。

用法示例:

 OrderExpression expression = new OrderExpression() .Add(x => x.Created, "created") .Add(x => x.Approved, "approved") .Add(x => x.Author.Name, "author"); IQueryable posts = _context.posts.AsQueryable(); posts = posts.OrderBy(expression, "+created", "-approved", "+author"); // OR posts = posts.OrderBy(expression, new string[]{"+created", "-approved", "+author"}); // OR posts = posts.OrderBy(expression, fields.ToArray[]); 

当然还有dotNetFiddle的现场演示

码:

 public class OrderExpressions { private readonly Dictionary _mappings = new Dictionary(); public OrderExpressions Add(Expression> expression, string keyword) { _mappings.Add(keyword, expression); return this; } public LambdaExpression this[string keyword] { get { return _mappings[keyword]; } } } public static class KeywordSearchExtender { private static readonly MethodInfo OrderbyMethod; private static readonly MethodInfo OrderbyDescendingMethod; private static readonly MethodInfo ThenByMethod; private static readonly MethodInfo ThenByDescendingMethod; //Here we use reflection to get references to the open generic methods for //the 4 Queryable methods that we need static KeywordSearchExtender() { OrderbyMethod = typeof(Queryable) .GetMethods() .First(x => x.Name == "OrderBy" && x.GetParameters() .Select(y => y.ParameterType.GetGenericTypeDefinition()) .SequenceEqual(new[] { typeof(IQueryable<>), typeof(Expression<>) })); OrderbyDescendingMethod = typeof(Queryable) .GetMethods() .First(x => x.Name == "OrderByDescending" && x.GetParameters() .Select(y => y.ParameterType.GetGenericTypeDefinition()) .SequenceEqual(new[] { typeof(IQueryable<>), typeof(Expression<>) })); ThenByMethod = typeof(Queryable) .GetMethods() .First(x => x.Name == "ThenBy" && x.GetParameters() .Select(y => y.ParameterType.GetGenericTypeDefinition()) .SequenceEqual(new[] { typeof(IOrderedQueryable<>), typeof(Expression<>) })); ThenByDescendingMethod = typeof(Queryable) .GetMethods() .First(x => x.Name == "ThenByDescending" && x.GetParameters() .Select(y => y.ParameterType.GetGenericTypeDefinition()) .SequenceEqual(new[] { typeof(IOrderedQueryable<>), typeof(Expression<>) })); } //This method can invoke OrderBy or the other methods without //getting as input the expression return value type private static IQueryable InvokeQueryableMethod( MethodInfo methodinfo, IQueryable queryable, LambdaExpression expression) { var generic_order_by = methodinfo.MakeGenericMethod( typeof(T), expression.ReturnType); return (IQueryable)generic_order_by.Invoke( null, new object[] { queryable, expression }); } public static IQueryable OrderBy(this IQueryable data, OrderExpressions mapper, params string[] arguments) { if (arguments.Length == 0) throw new ArgumentException(@"You need at least one argument!", "arguments"); List sorting = arguments.Select(a => new SortArgument(a)).ToList(); IQueryable result = null; for (int i = 0; i < sorting.Count; i++) { SortArgument sort = sorting[i]; LambdaExpression lambda = mapper[sort.Keyword]; if (i == 0) result = InvokeQueryableMethod(sort.Ascending ? OrderbyMethod : OrderbyDescendingMethod, data, lambda); else result = InvokeQueryableMethod(sort.Ascending ? ThenByMethod : ThenByDescendingMethod, result, lambda); } return result; } } public class SortArgument { public SortArgument() { } public SortArgument(string term) { if (term.StartsWith("-")) { Ascending = false; Keyword = term.Substring(1); } else if (term.StartsWith("+")) { Ascending = true; Keyword = term.Substring(1); } else { Ascending = true; Keyword = term; } } public string Keyword { get; set; } public bool Ascending { get; set; } } 

以下课程将帮助您完成此任务。 您可以找到内联代码的解释。

 public static class MyClass { public static IQueryable Order( IQueryable queryable, List fields, //We pass LambdaExpression because the selector property type can be anything Dictionary expressions) { //Start with input queryable IQueryable result = queryable; //Loop through fields for (int i = 0; i < fields.Count; i++) { bool ascending = fields[i][0] == '+'; string field = fields[i].Substring(1); LambdaExpression expression = expressions[field]; MethodInfo method = null; //Based on sort order and field index, determine which method to invoke if (ascending && i == 0) method = OrderbyMethod; else if (ascending && i > 0) method = ThenByMethod; else if (!ascending && i == 0) method = OrderbyDescendingMethod; else method = ThenByDescendingMethod; //Invoke appropriate method result = InvokeQueryableMethod( method, result, expression); } return result; } //This method can invoke OrderBy or the other methods without //getting as input the expression return value type private static IQueryable InvokeQueryableMethod( MethodInfo methodinfo, IQueryable queryable, LambdaExpression expression) { var generic_order_by = methodinfo.MakeGenericMethod( typeof(T), expression.ReturnType); return (IQueryable)generic_order_by.Invoke( null, new object[] { queryable, expression }); } private static readonly MethodInfo OrderbyMethod; private static readonly MethodInfo OrderbyDescendingMethod; private static readonly MethodInfo ThenByMethod; private static readonly MethodInfo ThenByDescendingMethod; //Here we use reflection to get references to the open generic methods for //the 4 Queryable methods that we need static MyClass() { OrderbyMethod = typeof(Queryable) .GetMethods() .First(x => x.Name == "OrderBy" && x.GetParameters() .Select(y => y.ParameterType.GetGenericTypeDefinition()) .SequenceEqual(new[] { typeof(IQueryable<>), typeof(Expression<>) })); OrderbyDescendingMethod = typeof(Queryable) .GetMethods() .First(x => x.Name == "OrderByDescending" && x.GetParameters() .Select(y => y.ParameterType.GetGenericTypeDefinition()) .SequenceEqual(new[] { typeof(IQueryable<>), typeof(Expression<>) })); ThenByMethod = typeof(Queryable) .GetMethods() .First(x => x.Name == "ThenBy" && x.GetParameters() .Select(y => y.ParameterType.GetGenericTypeDefinition()) .SequenceEqual(new[] { typeof(IOrderedQueryable<>), typeof(Expression<>) })); ThenByDescendingMethod = typeof(Queryable) .GetMethods() .First(x => x.Name == "ThenByDescending" && x.GetParameters() .Select(y => y.ParameterType.GetGenericTypeDefinition()) .SequenceEqual(new[] { typeof(IOrderedQueryable<>), typeof(Expression<>) })); } } 

以下是一个示例用法:

 public class Person { public int Age { get; set; } public string Name { get; set; } public override string ToString() { return Name + ", " + Age; } } class Program { static void Main(string[] args) { List persons = new List { new Person {Name = "yacoub", Age = 30}, new Person {Name = "yacoub", Age = 32}, new Person {Name = "adam", Age = 30}, new Person {Name = "adam", Age = 33}, }; var query = MyClass.Order( persons.AsQueryable(), new List { "+Name", "-Age" }, new Dictionary { {"Name", (Expression>) (x => x.Name)}, {"Age", (Expression>) (x => x.Age)} }); var result = query.ToList(); } } 

编辑:更改代码以与您的语法紧密匹配

此代码在客户端上排序,但适用于所有IEnumerables 如果您绝对需要对数据库进行排序,请查看Yacoub的static MyClass()以了解他是如何解决此问题的。

以下示例基于您提供的信息,您可能需要稍微调整一下。

 public class DemoClass { public DateTime Created { get; set; } public bool Approved { get; set; } public Person Author { get; set; } } public class Person { public string Name { get; set; } } 

由于您的示例包含实际解析为Author.Name ,因此您需要为关键字创建某种映射(就像使用OrderExpression类一样)。

 public class OrderExpressions { private readonly Dictionary> _mappings = new Dictionary>(); public OrderExpressions Add(Func expression, string keyword) { _mappings.Add(keyword, expression); return this; } public Func this[string keyword] { get { return _mappings[keyword]; } } } 

可以这样使用:

 OrderExpressions expressions = new OrderExpressions() .Add(x => x.Created, "created") .Add(x => x.Approved, "approved") .Add(x => x.Author.Name, "author"); 

您可以将这些函数/ lambda表达式直接传递给Linq并逐个添加下一个比较。 从OrderByOrderByDescrending开始,它将为您提供第一个IOrderedEnumerable ,然后使用ThenByThenByDescending添加所有剩余的参数。

 public static class KeywordSearchExtender { public static IOrderedEnumerable OrderBy(this IEnumerable data, OrderExpressions mapper, params string[] arguments) { if (arguments.Length == 0) throw new ArgumentException(@"You need at least one argument!", "arguments"); List sorting = arguments.Select(a => new SortArgument(a)).ToList(); IOrderedEnumerable result = null; for (int i = 0; i < sorting.Count; i++) { SortArgument sort = sorting[i]; Func lambda = mapper[sort.Keyword]; if (i == 0) result = sorting[i].Ascending ? data.OrderBy(lambda) : data.OrderByDescending(lambda); else result = sorting[i].Ascending ? result.ThenBy(lambda) : result.ThenByDescending(lambda); } return result; } } public class SortArgument { public SortArgument() { } public SortArgument(string term) { if (term.StartsWith("-")) { Ascending = false; Keyword = term.Substring(1); } else if (term.StartsWith("+")) { Ascending = true; Keyword = term.Substring(1); } else { Ascending = true; Keyword = term; } } public string Keyword { get; set; } public bool Ascending { get; set; } } 

它们一起使用如下:

 var data = WhateverYouDoToGetYourData(); var expressions = new OrderExpressions() .Add(x => x.Created, "created") .Add(x => x.Approved, "approved") .Add(x =>x.Author.Name, "author"); var result = data.OrderBy(expressions, "+created", "-approved", "+author"); // OR var result = data.OrderBy(expressions, fields); 

你可以在dotNetFiddle上找到我的概念validation 。