【问题标题】:How do I specify the Linq OrderBy argument dynamically? [duplicate]如何动态指定 Linq OrderBy 参数? [复制]
【发布时间】:2011-09-01 01:10:21
【问题描述】:

如何使用我作为参数的值指定传递给orderby 的参数?

例如:

List<Student> existingStudends = new List<Student>{ new Student {...}, new Student {...}}

目前实施:

List<Student> orderbyAddress = existingStudends.OrderBy(c => c.Address).ToList();

代替c.Address,如何将其作为参数?

例子

 string param = "City";
 List<Student> orderbyAddress = existingStudends.OrderByDescending(c => param).ToList();

【问题讨论】:

  • 您可能正在寻找动态 Linq:weblogs.asp.net/scottgu/archive/2008/01/07/…
  • @Nev_Rahd:试图澄清一下这个问题。此外,OrderBy 是一个 Linq 功能,位于 IEnumerable 上,而不是特定于 List 的功能。随意回滚编辑或进一步更改:)

标签: c# linq


【解决方案1】:

你可以使用一点反射来构造表达式树,如下所示(这是一种扩展方法):

public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string orderByProperty,
                          bool desc) 
{
     string command = desc ? "OrderByDescending" : "OrderBy";
     var type = typeof(TEntity);
     var property = type.GetProperty(orderByProperty);
     var parameter = Expression.Parameter(type, "p");
     var propertyAccess = Expression.MakeMemberAccess(parameter, property);
     var orderByExpression = Expression.Lambda(propertyAccess, parameter);
     var resultExpression = Expression.Call(typeof(Queryable), command, new Type[] { type, property.PropertyType },
                                   source.Expression, Expression.Quote(orderByExpression));
     return source.Provider.CreateQuery<TEntity>(resultExpression);
}

orderByProperty 是您要排序的属性名称,如果将 true 作为desc 的参数传递,将按降序排序;否则,将按升序排序。

现在您应该可以使用existingStudents.OrderBy("City",true);existingStudents.OrderBy("City",false);

【讨论】:

  • 这个答案很棒,比反射答案好得多。这实际上适用于实体框架等其他提供者。
  • 如果可以的话,我会投票十次!!!像这样写扩展方法哪里学的??!!
  • 这是否应该返回一个 IOrderedQueryable,就像内置的 OrderBy 一样?这样,你就可以调用 .ThenBy 了。
  • 这似乎在使用 EFCore 3.0 时不再起作用,我遇到了一个运行时错误,它无法翻译查询。
  • 是的,@Mildan,这对我来说也适用于 3.0 和 3.1。出现错误〜“无法翻译”。如果相关,我将 Pomelo 用于 MySQl。问题是表达式。如果您手动编码它可以工作的表达式。所以代替 Lambda.Expression() 只需提供类似的东西: LambdaExpression orderByExp1 = (Expression>)(x => x.Name);
【解决方案2】:

这是使用反射的可能性...

var param = "Address";    
var propertyInfo = typeof(Student).GetProperty(param);    
var orderByAddress = items.OrderBy(x => propertyInfo.GetValue(x, null));

【讨论】:

  • 但是当涉及到由提供者解释的 Linq 表达式时,它是真的吗,比如实体框架(sql server,或其他)??
  • @vijay - 使用ThenBy method
  • 当我尝试这个时,我得到错误:LINQ to Entities 无法识别方法 'System.Object GetValue(System.Object, System.Object[])' 方法,并且无法翻译此方法成商店表达式。这个答案是否仅适用于 Linq To SQL?
  • .AsEnumerable() 没有错误:var orderByAddress = items.AsEnumerable().OrderBy(x => propertyInfo.GetValue(x, null));
  • 如何动态决定按 asc 或 desc 排序
【解决方案3】:

扩展answer by @Icarus:如果您希望扩展方法的返回类型是 IOrderedQueryable 而不是 IQueryable,您可以简单地将结果转换如下:

public static IOrderedQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string orderByProperty, bool desc)
{
    string command = desc ? "OrderByDescending" : "OrderBy";
    var type = typeof(TEntity);
    var property = type.GetProperty(orderByProperty);
    var parameter = Expression.Parameter(type, "p");
    var propertyAccess = Expression.MakeMemberAccess(parameter, property);
    var orderByExpression = Expression.Lambda(propertyAccess, parameter);
    var resultExpression = Expression.Call(typeof(Queryable), command, new Type[] { type, property.PropertyType },
        source.Expression, Expression.Quote(orderByExpression));
    return (IOrderedQueryable<TEntity>)source.Provider.CreateQuery<TEntity>(resultExpression);
}

【讨论】:

  • 似乎其他答案不适合实体框架。这是 EF 的完美解决方案,因为 Linq to Entities 不支持 GetProperty、GetValue
  • 这种方法在 3.0 和 3.1 中对我来说似乎失败了(它在 2.2 中有效)。我将 Pomelo 用于 MySql,所以这可能是相关的。有一种解决方法,但它很难看。请参阅我上面的评论。
  • 这在 EF 3.0 中对我有用。但是,您应该更改以下行,以便前端不需要区分大小写: var property = type.GetProperty(OrderByProperty, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
  • 这是否仍然针对 Core 3.1 进行了优化?
【解决方案4】:

1) 安装System.Linq.Dynamic

2) 添加如下代码

public static class OrderUtils
{
    public static string ToStringForOrdering<T, TKey>(this Expression<Func<T, TKey>> expression, bool isDesc = false)
    {
        var str = expression.Body.ToString();
        var param = expression.Parameters.First().Name;
        str = str.Replace("Convert(", "(").Replace(param + ".", "");
        return str + (isDesc ? " descending" : "");
    }
}

3) 编写用于选择 Lambda 函数的开关

public static class SortHelper
{
    public static Expression<Func<UserApp, object>> UserApp(string orderProperty)
    {
        orderProperty = orderProperty?.ToLowerInvariant();
        switch (orderProperty)
        {
            case "firstname":
                return x => x.PersonalInfo.FirstName;
            case "lastname":
                return x => x.PersonalInfo.LastName;
            case "fullname":
                return x => x.PersonalInfo.FirstName + x.PersonalInfo.LastName;
            case "email":
                return x => x.Email;

        }
    }
}

4) 使用你的助手

Dbset.OrderBy(SortHelper.UserApp("firstname").ToStringForOrdering())

5) 您可以将它与分页一起使用 (PagedList)

public virtual  IPagedList<T> GetPage<TOrder>(Page page, Expression<Func<T, bool>> where, Expression<Func<T, TOrder>> order, bool isDesc = false,
      params Expression<Func<T, object>>[] includes)
    {
        var orderedQueryable = Dbset.OrderBy(order.ToStringForOrdering(isDesc));
        var query = orderedQueryable.Where(where).GetPage(page);
        query = AppendIncludes(query, includes);

        var results = query.ToList();
        var total =  Dbset.Count(where);

        return new StaticPagedList<T>(results, page.PageNumber, page.PageSize, total);
    }

说明

System.Linq.Dynamic 允许我们在 OrderBy 方法中设置字符串值。但是在这个扩展中,字符串将被解析为 Lambda。所以我认为如果我们将 Lambda 解析为字符串并将其提供给 OrderBy 方法,它会起作用。它有效!

【讨论】:

    【解决方案5】:
       private Func<T, object> GetOrderByExpression<T>(string sortColumn)
        {
            Func<T, object> orderByExpr = null;
            if (!String.IsNullOrEmpty(sortColumn))
            {
                Type sponsorResultType = typeof(T);
    
                if (sponsorResultType.GetProperties().Any(prop => prop.Name == sortColumn))
                {
                    System.Reflection.PropertyInfo pinfo = sponsorResultType.GetProperty(sortColumn);
                    orderByExpr = (data => pinfo.GetValue(data, null));
                }
            }
            return orderByExpr;
        }
    
        public List<T> OrderByDir<T>(IEnumerable<T> source, string dir, Func<T, object> OrderByColumn)
        {
            return dir.ToUpper() == "ASC" ? source.OrderBy(OrderByColumn).ToList() : source.OrderByDescending(OrderByColumn).ToList();``
        }
    
     // Call the code like below
            var orderByExpression= GetOrderByExpression<SearchResultsType>(sort);
    
        var data = OrderByDir<SponsorSearchResults>(resultRecords, SortDirectionString, orderByExpression);    
    

    【讨论】:

    • 太棒了!正是我需要的。
    【解决方案6】:

    这是我想出的处理条件降序的方法。您可以将其与动态生成keySelector func 的其他方法结合使用。

        public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>(this IQueryable<TSource> source,
                System.Linq.Expressions.Expression<Func<TSource, TKey>> keySelector,
                System.ComponentModel.ListSortDirection sortOrder
                )
        {
            if (sortOrder == System.ComponentModel.ListSortDirection.Ascending)
                return source.OrderBy(keySelector);
            else
                return source.OrderByDescending(keySelector);
        }
    

    用法:

    //imagine this is some parameter
    var direction = System.ComponentModel.ListSortDirection.Ascending;
    query = query.OrderBy(ec => ec.MyColumnName, direction);
    

    请注意,这允许您将此带有新参数的 .OrderBy 扩展链接到任何 IQueryable。

    // perhaps passed in as a request of user to change sort order
    // var direction = System.ComponentModel.ListSortDirection.Ascending;
    query = context.Orders
            .Where(o => o.Status == OrderStatus.Paid)
            .OrderBy(ec => ec.OrderPaidUtc, direction);
    

    【讨论】:

      【解决方案7】:

      这不会让您传递string,正如您在问题中所要求的那样,但它可能仍然适合您。

      OrderByDescending 方法采用Func&lt;TSource, TKey&gt;,因此您可以这样重写您的函数:

      List<Student> QueryStudents<TKey>(Func<Student, TKey> orderBy)
      {
          return existingStudents.OrderByDescending(orderBy).ToList();
      }
      

      OrderByDescending 还有其他重载,它们采用Expression&lt;Func&lt;TSource, TKey&gt;&gt; 和/或IComparer&lt;TKey&gt;。您也可以查看这些内容,看看它们是否为您提供了任何有用的东西。

      【讨论】:

      • 这不起作用,因为您没有定义 TKey 的类型。您必须将您的 改为使用
      • 这对我有用!我想要一个函数,它可以根据传递的 bool 值对列表进行升序或降序排序。稍作调整,您的代码运行良好!
      • LINQ in Action: IEnumerable CustomSort(Func 选择器,布尔升序) { IEnumerable books = SampleData.Books;返回升序? book.OrderBy(selector) : books.OrderByDescending(selector); }
      【解决方案8】:

      对我有用的唯一解决方案是由 neoGeneva 发布在这里 https://gist.github.com/neoGeneva/1878868

      我将重新发布他的代码,因为它运行良好,我不希望它在互联网中丢失!

          public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string sortExpression)
          {
              if (source == null)
                  throw new ArgumentNullException("source", "source is null.");
      
              if (string.IsNullOrEmpty(sortExpression))
                  throw new ArgumentException("sortExpression is null or empty.", "sortExpression");
      
              var parts = sortExpression.Split(' ');
              var isDescending = false;
              var propertyName = "";
              var tType = typeof(T);
      
              if (parts.Length > 0 && parts[0] != "")
              {
                  propertyName = parts[0];
      
                  if (parts.Length > 1)
                  {
                      isDescending = parts[1].ToLower().Contains("esc");
                  }
      
                  PropertyInfo prop = tType.GetProperty(propertyName);
      
                  if (prop == null)
                  {
                      throw new ArgumentException(string.Format("No property '{0}' on type '{1}'", propertyName, tType.Name));
                  }
      
                  var funcType = typeof(Func<,>)
                      .MakeGenericType(tType, prop.PropertyType);
      
                  var lambdaBuilder = typeof(Expression)
                      .GetMethods()
                      .First(x => x.Name == "Lambda" && x.ContainsGenericParameters && x.GetParameters().Length == 2)
                      .MakeGenericMethod(funcType);
      
                  var parameter = Expression.Parameter(tType);
                  var propExpress = Expression.Property(parameter, prop);
      
                  var sortLambda = lambdaBuilder
                      .Invoke(null, new object[] { propExpress, new ParameterExpression[] { parameter } });
      
                  var sorter = typeof(Queryable)
                      .GetMethods()
                      .FirstOrDefault(x => x.Name == (isDescending ? "OrderByDescending" : "OrderBy") && x.GetParameters().Length == 2)
                      .MakeGenericMethod(new[] { tType, prop.PropertyType });
      
                  return (IQueryable<T>)sorter
                      .Invoke(null, new object[] { source, sortLambda });
              }
      
              return source;
          }
      

      【讨论】:

        【解决方案9】:
        • 将 nugget 包 Dynamite 添加到您的代码中

        • 添加命名空间Dynamite.Extensions 例如:使用 Dynamite.Extensions;

        • 像任何 SQL 查询一样按查询给出订单 例如:students.OrderBy("City DESC, Address").ToList();

        【讨论】:

          【解决方案10】:

          扩展@Icarus 的响应:如果您想按两个字段排序,我可以执行以下功能(对于一个字段,Icarus 的响应效果很好)。

          public static IQueryable<T> OrderByDynamic<T>(this IQueryable<T> q, string SortField1, string SortField2, bool Ascending)
                  {
                      var param = Expression.Parameter(typeof(T), "p");
                      var body = GetBodyExp(SortField1, SortField2, param);
                      var exp = Expression.Lambda(body, param);
          
                      string method = Ascending ? "OrderBy" : "OrderByDescending";
                      Type[] types = new Type[] { q.ElementType, exp.Body.Type };
                      var mce = Expression.Call(typeof(Queryable), method, types, q.Expression, exp);
                      return q.Provider.CreateQuery<T>(mce);
                  }
          

          这是body为lambda表达式返回的函数,它适用于string和int,但根据每个程序员的需要添加更多类型就足够了

          public static NewExpression GetBodyExp(string field1, string field2, ParameterExpression Parametro)
                  {    
                      // SE OBTIENE LOS NOMBRES DE LOS TIPOS DE VARIABLE 
                      string TypeName1 = Expression.Property(Parametro, field1).Type.Name;
                      string TypeName2 = Expression.Property(Parametro, field2).Type.Name;
          
                      // SE DECLARA EL TIPO ANONIMO SEGUN LOS TIPOS DE VARIABLES
                      Type TypeAnonymous = null;
                      if (TypeName1 == "String")
                      {
                          string var1 = "0";
                          if (TypeName2 == "Int32")
                          {
                              int var2 = 0;
                              var example = new { var1, var2 };
                              TypeAnonymous = example.GetType();
                          }
          
                          if (TypeName2 == "String")
                          {
                              string var2 = "0";
                              var example = new { var1, var2 };
                              TypeAnonymous = example.GetType();
                          }    
                      }    
          
                      if (TypeName1 == "Int32")
                      {
                          int var1 = 0;
                          if (TypeName2 == "Int32")
                          {
                              string var2 = "0";
                              var example = new { var1, var2 };
                              TypeAnonymous = example.GetType();
                          }
          
                          if (TypeName2 == "String")
                          {
                              string var2 = "0";
                              var example = new { var1, var2 };
                              TypeAnonymous = example.GetType();
                          }    
                      }
          
                      //se declaran los TIPOS NECESARIOS PARA GENERAR EL BODY DE LA EXPRESION LAMBDA
                      MemberExpression[] args = new[] { Expression.PropertyOrField(Parametro, field1), Expression.PropertyOrField(Parametro, field2) };
                      ConstructorInfo CInfo = TypeAnonymous.GetConstructors()[0];
                      IEnumerable<MemberInfo> a = TypeAnonymous.GetMembers().Where(m => m.MemberType == MemberTypes.Property);
          
                      //BODY 
                      NewExpression body = Expression.New(CInfo, args, TypeAnonymous.GetMembers().Where(m => m.MemberType == MemberTypes.Property));
          
                      return body;
                  }
          

          使用它完成以下操作

          IQueryable<MyClass> IqMyClass= context.MyClass.AsQueryable();
          List<MyClass> ListMyClass= IqMyClass.OrderByDynamic("UserName", "IdMyClass", true).ToList();
          

          如果有更好的方法,如果他们分享它会很棒

          感谢:How can I make a Multiple property lambda expression with Linq

          【讨论】:

            【解决方案11】:

            新答案:这是一个更完整的答案,支持 多个 列,以便按 SQL 进行排序。示例:.OrderBy("FirstName,Age DESC")

                public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string orderByProperty, bool desc, bool isThenBy = false)
                {
                    string command = isThenBy ? (desc ? "ThenByDescending" : "ThenBy") : (desc ? "OrderByDescending" : "OrderBy");
                    var type = typeof(TEntity);
                    var property = type.GetProperty(orderByProperty);
                    var parameter = Expression.Parameter(type, "p");
                    var propertyAccess = Expression.MakeMemberAccess(parameter, property);
                    var orderByExpression = Expression.Lambda(propertyAccess, parameter);
                    var resultExpression = Expression.Call(typeof(Queryable), command, new Type[] { type, property.PropertyType },
                                                  source.Expression, Expression.Quote(orderByExpression));
                    return source.Provider.CreateQuery<TEntity>(resultExpression);
                }
            
                public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string sqlOrderByList)
                {
                    var ordebyItems = sqlOrderByList.Trim().Split(',');
                    IQueryable<TEntity> result = source;
                    bool useThenBy = false;
                    foreach (var item in ordebyItems)
                    {
                        var splt = item.Trim().Split(' ');
                        result = result.OrderBy(splt[0].Trim(), (splt.Length > 1 && splt[1].Trim().ToLower() == "desc"), useThenBy);
                        if (useThenBy)
                            useThenBy = true;
                    }
                    return result;
                }
            

            第二个函数遍历orderby 列并使用第一个。

            【讨论】:

              【解决方案12】:

              我迟到了,但这些解决方案都不适合我。我很想尝试 System.Linq.Dynamic,但我在 Nuget 上找不到,可能是贬值了?不管怎样……

              这是我想出的解决方案。我需要动态混合使用 OrderByOrderByDescendingOrderBy > ThenBy

              我只是为我的列表对象创建了一个扩展方法,我知道这有点笨拙...如果我经常这样做,我不会推荐这个,但它对一次性有好处。

              List<Employee> Employees = GetAllEmployees();
              
              foreach(Employee oEmployee in Employees.ApplyDynamicSort(eEmployeeSort))
              {
                  //do stuff
              }
              
              public static IOrderedEnumerable<Employee> ApplyDynamicSort(this List<Employee> lEmployees, Enums.EmployeeSort eEmployeeSort)
              {
                  switch (eEmployeeSort)
                  {
                      case Enums.EmployeeSort.Name_ASC:
                          return lEmployees.OrderBy(x => x.Name);
                      case Enums.EmployeeSort.Name_DESC:
                          return lEmployees.OrderByDescending(x => x.Name);
                      case Enums.EmployeeSort.Department_ASC_Salary_DESC:
                          return lEmployees.OrderBy(x => x.Department).ThenByDescending(y => y.Salary);
                      default:
                          return lEmployees.OrderBy(x => x.Name);
                  }
              }
              

              【讨论】:

                猜你喜欢
                • 2014-06-02
                • 2023-03-09
                • 1970-01-01
                • 2016-03-18
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多