如何动态指定Linq OrderBy参数?


94

如何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();

4
你可能会寻找动态的LINQ:weblogs.asp.net/scottgu/archive/2008/01/07/...
BrokenGlass

@Nev_Rahd:试图澄清一下这个问题。另外,OrderBy是Linq功能,并且处于on状态IEnumerable,而不是特定于的功能List。随时回滚编辑或进一步更改它:)
Merlyn Morgan-Graham

Answers:


129

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

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

3
但是当涉及到由提供程序解释的Linq表达式时,是真的吗?例如Entity Framework(SQL Server或其他)?
a.boussema 2013年

2
@vijay-使用ThenBy方法
codeConcussion

7
当我尝试此操作时,出现错误:LINQ to Entities无法识别方法'System.Object GetValue(System.Object,System.Object [])'方法,并且该方法无法转换为商店表达式。此答案仅适用于Linq To SQL吗?
philreed

4
.AsEnumerable()没有错误:var orderByAddress = items.AsEnumerable()。OrderBy(x => propertyInfo.GetValue(x,null));
凯撒

1
我如何动态决定按升序还是降序排序
Hitesh Modha 2015年

123

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

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);


10
这个答案很棒,比反射答案好得多。实际上,这可以与其他提供程序(例如实体框架)一起使用。
山姆

2
如果可以的话,我会投票十次!您在哪里学习编写这样的扩展方法?
雅克(Jach)

3
是否应该像内置的OrderBy一样返回IOrderedQueryable?这样,您可以调用.ThenBy。
Patrick Szalapski

4
当使用EFCore 3.0时,这似乎不再起作用,我遇到了运行时错误,无法转换查询。
米尔丹

3
是的,@ Mildan,这对我也适用于3.0和3.1。错误〜“无法翻译”。如果相关的话,我将Pomelo用于MySQl。问题是表达式。如果您手动编码它起作用的表达式。因此,除了提供Lambda.Expression()之外,还可以提供以下类似的内容:LambdaExpression orderByExp1 =(Expression <Func <AgencySystemBiz,string >>)(x => x.Name);
威胁

10

通过@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);
}

2
似乎其他答案不适用于实体框架。这是EF的完美解决方案,因为Linq to Entities不支持GetProperty,GetValue
Bill

1
这种方法对我来说在3.0和3.1中似乎失败了(在2.2中有效)。我将Pomelo用于MySql,所以可能相关。周围有工作,但是很丑。请参阅上面的评论。
威胁

这在EF 3.0中对我有用。但是,您应该更改以下行,以便前端无需区分大小写: var属性= type.GetProperty(OrderByProperty,BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
亚瑟王3

是否仍针对Core 3.1进行了优化?
克里斯·高(Chris Go)

8

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)您可以在Pagging(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方法,那将是可行的。而且有效!


6
   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);    

辉煌!正是我所需要的。
布兰登格里芬

5

这是我处理条件降序时想到的。您可以将其与keySelector动态生成函数的其他方法结合起来。

    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);

3

这不会让您通过 string正如您在问题中所要求的那样,,但是它可能仍然对您有用。

OrderByDescending方法带有Func<TSource, TKey>,因此您可以通过以下方式重写函数:

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

也有其他重载OrderByDescending,需要Expression<Func<TSource, TKey>>加上和和/或IComparer<TKey>。您还可以查看这些内容,看看它们是否为您提供任何使用。


这不起作用,因为您没有定义TKey的类型。您必须将<T>更改为具有<TKey>。
Patrick Desjardins 2014年

这正是对我有用的!我想要一个函数,该函数根据传递的布尔值对列表进行升序或降序排序。稍加调整,您的代码就可以很好地工作!
Joe Gayetty

LINQ的作用:IEnumerable <Book> CustomSort <TKey>(Func <Book,TKey>选择器,布尔值升序){IEnumerable <Book> books = SampleData.Books; 返回升序?books.OrderBy(selector):books.OrderByDescending(selector); }
Leszek P

1

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;
    }

1
  • 将块包Dynamite添加到代码中

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

  • 像任何SQL查询一样,通过查询给出订单,例如:students.OrderBy(“ City DESC,Address”)。ToList();


1

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

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);
        }

这是主体为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();

如果有更好的方法可以做到这一点,那么如果他们共享它会很棒

由于以下原因,我设法解决了问题:如何使用Linq进行多属性lambda表达式


-1

我参加聚会很晚,但是这些解决方案都不适合我。我很想尝试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);
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.