Come posso specificare dinamicamente l'argomento Linq OrderBy?


94

Come si specifica l'argomento passato orderbyutilizzando un valore che prendo come parametro?

Ex:

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

Attualmente implementazione:

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

Invece di c.Address, come posso prenderlo come parametro?

Esempio

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


@Nev_Rahd: Ho cercato di chiarire un po 'la domanda. Inoltre, OrderByè una funzionalità di Linq ed è attiva IEnumerable, non una funzionalità specifica per List. Sentiti libero di ripristinare la modifica o cambiarla ulteriormente :)
Merlyn Morgan-Graham

Risposte:


129

Ecco una possibilità usando la riflessione ...

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

3
Ma è vero quando si tratta di espressioni Linq interpretate da provider, come Entity Framework (sql server o altro) ??
Boussema

2
@vijay - usa il ThenBymetodo .
codeConcussion

7
Quando provo, ottengo l'errore: LINQ to Entities non riconosce il metodo "System.Object GetValue (System.Object, System.Object [])" e questo metodo non può essere tradotto in un'espressione di archivio. Questa risposta si applica solo a Linq To SQL?
philreed

4
Nessun errore con .AsEnumerable (): var orderByAddress = items.AsEnumerable (). OrderBy (x => propertyInfo.GetValue (x, null));
Cesare

1
Come posso decidere dinamicamente di ordinare per asc o desc
Hitesh Modha

123

Puoi usare un po 'di riflessione per costruire l'albero delle espressioni come segue (questo è un metodo di estensione):

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è il nome della proprietà in base al quale si desidera ordinare e se si passa true come parametro desc, verrà ordinato in ordine decrescente; in caso contrario, verrà ordinato in ordine crescente.

Ora dovresti essere in grado di fare existingStudents.OrderBy("City",true);oexistingStudents.OrderBy("City",false);


10
Questa risposta è fantastica e molto meglio della risposta riflessa. Funziona effettivamente con altri provider come il framework di entità.
Sam

2
Lo voterei dieci volte se potessi !!! Dove impari a scrivere un metodo di estensione come questo ?? !!
Jach

3
Questo dovrebbe restituire un IOrderedQueryable, proprio come il OrderBy integrato? In questo modo, potresti chiamare .ThenBy su di esso.
Patrick Szalapski

4
Questo sembra non funzionare più quando si utilizza EFCore 3.0, ricevo un errore di runtime in cui non è possibile tradurre la query.
Mildan

3
Sì, @Mildan, anche per me questo rompe con 3.0 e 3.1. con l'errore ~ ​​"non posso tradurre". Uso Pomelo per MySQl, se pertinente. Il problema è l'espressione. SE si codifica a mano l'espressione funziona. Quindi, invece di Lambda.Expression (), fornisci semplicemente qualcosa di simile: LambdaExpression orderByExp1 = (Expression <Func <AgencySystemBiz, string >>) (x => x.Name);
Minaccia il

10

Per espandere la risposta di @Icarus : se vuoi che il tipo restituito del metodo di estensione sia un IOrderedQueryable invece di un IQueryable, puoi semplicemente eseguire il cast del risultato come segue:

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
Sembra che altre risposte non fossero appropriate per Entity Framework. Questa è una soluzione perfetta per EF poiché Linq to Entities non supporta GetProperty, GetValue
Bill

1
Questo metodo sembra fallire per me in 3.0 e 3.1 (ha funzionato in 2.2). Uso Pomelo per MySql, quindi potrebbe essere rilevante. C'è un lavoro in giro ma è brutto. Vedi il mio commento sopra.
Minaccia

Questo ha funzionato per me in EF 3.0. Tuttavia, è necessario modificare la riga seguente in modo che il front-end non debba corrispondere alla distinzione tra maiuscole e minuscole: var property = type.GetProperty (OrderByProperty, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
King Arthur il terzo

È ancora ottimizzato per Core 3.1?
Chris Go

8

1) Installa System.Linq.Dynamic

2) Aggiungi il seguente codice

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) Scrivi il tuo interruttore per la selezione della funzione 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 i tuoi aiutanti

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

5) Puoi usarlo con il 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);
    }

Spiegazione

System.Linq.Dynamic ci consente di impostare il valore della stringa nel metodo OrderBy. Ma all'interno di questa estensione la stringa verrà analizzata in Lambda. Quindi ho pensato che avrebbe funzionato se analizzassimo Lambda in stringa e lo assegnassimo al metodo OrderBy. E funziona!


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

Brillante! Esattamente quello di cui avevo bisogno.
Brandon Griffin

5

Ecco qualcosa che mi è venuto in mente per affrontare un Discendente condizionale. È possibile combinarlo con altri metodi per generare keySelectordinamicamente la funzione.

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

Utilizzo:

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

Si noti che questo consente di concatenare questa .OrderByestensione con un nuovo parametro su qualsiasi 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

Questo non ti consente di superare un string, come hai chiesto nella tua domanda, ma potrebbe comunque funzionare per te.

Il OrderByDescendingmetodo richiede un Func<TSource, TKey>, quindi puoi riscrivere la tua funzione in questo modo:

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

Ci sono anche altri sovraccarichi OrderByDescendingche richiedono a Expression<Func<TSource, TKey>>, e / o a IComparer<TKey>. Potresti anche esaminarli e vedere se ti forniscono qualcosa di utile.


Questo non funziona perché non si definisce il tipo di TKey. Devi cambiare il tuo <T> per avere invece <TKey>.
Patrick Desjardins

Questo era proprio quello che ha funzionato per me! Volevo una funzione che ordinasse un elenco in ordine crescente o decrescente, a seconda di un valore bool passato. Il tuo codice ha funzionato alla grande con un piccolo ritocco!
Joe Gayetty

LINQ in azione: IEnumerable <Book> CustomSort <TKey> (Func <Book, TKey> selector, Boolean crescente) {IEnumerable <Book> books = SampleData.Books; ritorno ascendente? books.OrderBy (selector): books.OrderByDescending (selector); }
Leszek P

1

L'unica soluzione che ha funzionato per me è stata pubblicata qui https://gist.github.com/neoGeneva/1878868 da neoGeneva.

Ripubblicherò il suo codice perché funziona bene e non vorrei che si perdesse negli interweb!

    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
  • Aggiungi il pacchetto di pepite Dynamite al tuo codice

  • Aggiungere lo spazio dei nomi Dynamite.Extensions Es: using Dynamite.Extensions;

  • Assegna un ordine per query come qualsiasi query SQL Ad esempio: students.OrderBy ("City DESC, Address"). ToList ();


1

Per estendere la risposta di @Icarus: se vuoi ordinare per due campi potrei svolgere la seguente funzione (per un campo la risposta di Icarius funziona molto bene).

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

Questa è la funzione che il body restituisce per l'espressione lambda, funziona con string e int, ma è sufficiente aggiungere più tipi per farla funzionare secondo le necessità di ogni programmatore

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

per usarlo si fa quanto segue

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

se c'è un modo migliore per farlo, sarebbe fantastico se lo condividessero

Sono riuscito a risolverlo grazie a: Come posso creare un'espressione lambda di proprietà multiple con Linq


-2

Sono arrivato in ritardo alla festa ma nessuna di queste soluzioni ha funzionato per me. Non vedevo l'ora di provare System.Linq.Dynamic, ma non sono riuscito a trovarlo su Nuget, forse deprezzato? In entrambi i casi...

Ecco una soluzione che ho trovato. Avevo bisogno di utilizzare dinamicamente una combinazione di OrderBy , OrderByDescending e OrderBy> ThenBy .

Ho semplicemente creato un metodo di estensione per il mio oggetto elenco, un po 'hacky lo so ... Non lo consiglierei se fosse qualcosa che stavo facendo molto, ma è buono per uno fuori.

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);
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.