¿Cómo especifico el argumento Linq OrderBy de forma dinámica?

94

¿Cómo especifico el argumento pasado para orderbyusar un valor que tomo como parámetro?

Ex:

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

Implementación actual:

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

En lugar de c.Address, ¿cómo puedo tomar eso como parámetro?

Ejemplo

 string param = "City";
 List<Student> orderbyAddress = existingStudends.OrderByDescending(c => param).ToList();
Sreedhar
fuente
4
Es posible que esté buscando Dynamic Linq: weblogs.asp.net/scottgu/archive/2008/01/07/…
BrokenGlass
@Nev_Rahd: Traté de aclarar un poco la pregunta. Además, OrderByes una función de Linq y está activada IEnumerable, no una función específica de List. Siéntase libre de revertir la edición o cambiarla más :)
Merlyn Morgan-Graham
Posible duplicado de Dynamic LINQ OrderBy en IEnumerable <T>
Michael Freidgeim

Respuestas:

128

Aquí hay una posibilidad usando la reflexión ...

var param = "Address";    
var propertyInfo = typeof(Student).GetProperty(param);    
var orderByAddress = items.OrderBy(x => propertyInfo.GetValue(x, null));
código de conmoción
fuente
3
Pero, ¿es cierto cuando se trata de expresiones Linq interpretadas por proveedores, como Entity Framework (servidor SQL u otro)?
a.boussema
2
@vijay: usa el ThenBymétodo .
codeConcussion
7
Cuando intento esto, aparece el error: LINQ to Entities no reconoce el método 'System.Object GetValue (System.Object, System.Object [])', y este método no se puede traducir a una expresión de tienda. ¿Esta respuesta solo se aplica a Linq To SQL?
philreed
4
No hay error con .AsEnumerable (): var orderByAddress = items.AsEnumerable (). OrderBy (x => propertyInfo.GetValue (x, null));
César
1
¿Cómo puedo decidir dinámicamente ordenar por asc o desc?
Hitesh Modha
122

Puede usar un poco de reflexión para construir el árbol de expresión de la siguiente manera (este es un método de extensión):

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

orderByPropertyes el nombre de propiedad por el que desea ordenar y, si pasa verdadero como parámetro desc, se ordenará en orden descendente; de lo contrario, se ordenará en orden ascendente.

Ahora deberías poder hacer existingStudents.OrderBy("City",true);oexistingStudents.OrderBy("City",false);

Ícaro
fuente
10
Esta respuesta es asombrosa y mucho mejor que la respuesta de reflexión. En realidad, esto funciona con otros proveedores como el marco de la entidad.
Sam
2
¡¡¡Votaría esto diez veces si pudiera !!! ¿Dónde aprendes a escribir un método de extensión como este?
Jach
3
¿Debería devolver un IOrderedQueryable, al igual que el OrderBy integrado? De esa forma, podrías llamar a .ThenBy.
Patrick Szalapski
4
Esto parece que ya no funciona cuando uso EFCore 3.0, obtengo un error de tiempo de ejecución en el que no puedo traducir la consulta.
Mildan
3
Sí, @Mildan, esto también se rompe en 3.0 y 3.1 para mí. con el error ~ "no se puede traducir". Yo uso Pomelo para MySQl si eso es relevante. El problema es la Expresión. SI codifica a mano la expresión, funciona. Entonces, en lugar de Lambda.Expression () simplemente proporcione algo como: LambdaExpression orderByExp1 = (Expression <Func <AgencySystemBiz, string >>) (x => x.Name);
Amenaza
10

Para ampliar la respuesta de @Icarus : si desea que el tipo de retorno del método de extensión sea un IOrderedQueryable en lugar de un IQueryable, simplemente puede emitir el resultado de la siguiente manera:

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);
}
Ciaran
fuente
2
Parece que otras respuestas no fueron apropiadas para Entity Framework. Esta es una solución perfecta para EF ya que Linq to Entities no admite GetProperty, GetValue
Bill
1
Este método parece fallarme en 3.0 y 3.1 (funcionó en 2.2). Utilizo Pomelo para MySql, por lo que podría ser relevante. Hay una solución, pero es fea. Vea mi comentario arriba.
Amenaza
Esto funcionó para mí en EF 3.0. Sin embargo, debe cambiar la siguiente línea para que el front-end no tenga que coincidir con la distinción entre mayúsculas y minúsculas: var property = type.GetProperty (OrderByProperty, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
King Arthur III
¿Sigue optimizado para Core 3.1?
Chris Go
8

1) Instalar System.Linq.Dynamic

2) Agrega el siguiente código

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) Escriba su interruptor para seleccionar la función 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) Usa tus ayudantes

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

5) Puede usarlo con paginación ( 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);
    }

Explicación

System.Linq.Dynamic nos permite establecer un valor de cadena en el método OrderBy. Pero dentro de esta extensión, la cadena se analizará en Lambda. Así que pensé que funcionaría si analizamos Lambda en una cadena y se la damos al método OrderBy. ¡Y funciona!

Igor Valikovsky
fuente
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);    
Rakesh Suryawanshi
fuente
¡Brillante! Exactamente lo que necesitaba.
Brandon Griffin
5

Aquí hay algo que se me ocurrió para lidiar con un Descendente condicional. Puede combinar esto con otros métodos para generar la keySelectorfunción de forma dinámica.

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

Uso:

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

Tenga en cuenta que esto le permite encadenar esta .OrderByextensión con un nuevo parámetro en cualquier 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);
AaronLS
fuente
3

Esto no le permite aprobar una string, como pidió en su pregunta, pero aún podría funcionar para usted.

El OrderByDescendingmétodo toma a Func<TSource, TKey>, por lo que puede reescribir su función de esta manera:

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

También hay otras sobrecargas OrderByDescendingque toman a Expression<Func<TSource, TKey>>, y / o a IComparer<TKey>. También puede mirarlos y ver si le proporcionan algo de utilidad.

Merlyn Morgan-Graham
fuente
Esto no funciona porque no define el tipo de TKey. Tienes que cambiar tu <T> para tener <TKey> en su lugar.
Patrick Desjardins
¡Esto fue lo que funcionó para mí! Quería una función que ordenara una lista ascendente o descendente, dependiendo de un valor bool pasado. ¡Tu código funcionó muy bien con un pequeño ajuste!
Joe Gayetty
LINQ en acción: IEnumerable <Book> CustomSort <TKey> (selector Func <Book, TKey>, booleano ascendente) {IEnumerable <Book> books = SampleData.Books; volver ascendiendo? libros.OrderBy (selector): libros.OrderByDescending (selector); }
Leszek P
1

La única solución que funcionó para mí fue publicada aquí https://gist.github.com/neoGeneva/1878868 por neoGeneva.

¡Volveré a publicar su código porque funciona bien y no me gustaría que se perdiera en las redes!

    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;
    }
usuario1477388
fuente
1
  • Agregue el paquete de pepitas Dynamite a su código

  • Agregue el espacio de nombres Dynamite.Extensions Ej .: usando Dynamite.Extensions;

  • Dar Orden por consulta como cualquier consulta SQL Ej .: estudiantes.OrderBy ("Ciudad DESC, Dirección"). ToList ();

Sreeja SJ
fuente
1

Para extender la respuesta de @Icarus: si desea ordenar por dos campos, podría realizar la siguiente función (para un campo, la respuesta de Icarius funciona muy bien).

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

Esta es la función que devuelve el cuerpo para la expresión lambda, funciona con string e int, pero basta con agregar más tipos para que funcione según la necesidad de cada programador

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

para usarlo se hace lo siguiente

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

si hay una mejor manera de hacer esto, sería genial si lo compartieran

Logré resolverlo gracias a: ¿Cómo puedo hacer una expresión lambda de propiedad múltiple con Linq?

Ruben hidalgo
fuente
-1

Llego muy tarde a la fiesta, pero ninguna de estas soluciones funcionó para mí. Estaba ansioso por probar System.Linq.Dynamic, pero no pude encontrar eso en Nuget, ¿tal vez depreciado? De cualquier manera...

Aquí hay una solución que se me ocurrió. Necesitaba usar dinámicamente una combinación de OrderBy , OrderByDescending y OrderBy> ThenBy .

Simplemente creé un método de extensión para mi objeto de lista, un poco hacky, lo sé ... No recomendaría esto si fuera algo que estuviera haciendo mucho, pero es bueno para una sola vez.

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