Wie gebe ich das Linq OrderBy-Argument dynamisch an?

94

Wie gebe ich das Argument an, das an orderbyeinen Wert übergeben wird, den ich als Parameter nehme?

Ex:

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

Derzeitige Implementierung:

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

Statt c.Address, wie kann ich das als Parameter übernehmen?

Beispiel

 string param = "City";
 List<Student> orderbyAddress = existingStudends.OrderByDescending(c => param).ToList();
Sreedhar
quelle
4
Möglicherweise suchen Sie nach Dynamic Linq: weblogs.asp.net/scottgu/archive/2008/01/07/…
BrokenGlass
@Nev_Rahd: Versucht, die Frage ein wenig zu klären. Außerdem OrderByhandelt es sich um eine Linq-Funktion, die aktiviert ist und IEnumerablekeine spezifische Funktion für List. Fühlen Sie sich frei, die Bearbeitung zurückzusetzen oder weiter zu ändern :)
Merlyn Morgan-Graham
Mögliches Duplikat von Dynamic LINQ OrderBy auf IEnumerable <T>
Michael Freidgeim

Antworten:

128

Hier ist eine Möglichkeit mit Reflexion ...

var param = "Address";    
var propertyInfo = typeof(Student).GetProperty(param);    
var orderByAddress = items.OrderBy(x => propertyInfo.GetValue(x, null));
codeConcussion
quelle
3
Aber ist es wahr, wenn es um Linq-Ausdrücke geht, die von Anbietern wie Entity Framework (SQL Server oder andere) interpretiert werden?
a.boussema
2
@vijay - benutze die ThenByMethode .
CodeConcussion
7
Wenn ich dies versuche, wird folgende Fehlermeldung angezeigt: LINQ to Entities erkennt die Methode 'System.Object GetValue (System.Object, System.Object [])' nicht und diese Methode kann nicht in einen Speicherausdruck übersetzt werden. Gilt diese Antwort nur für Linq To SQL?
Philreed
4
Kein Fehler mit .AsEnumerable (): var orderByAddress = items.AsEnumerable (). OrderBy (x => propertyInfo.GetValue (x, null));
Caesar
1
Wie kann ich mich dynamisch entscheiden, nach auf- oder absteigend zu bestellen
Hitesh Modha
122

Sie können den Ausdrucksbaum mit ein wenig Reflexion wie folgt erstellen (dies ist eine Erweiterungsmethode):

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

orderByPropertyist der Eigenschaftsname, nach dem Sie bestellen möchten, und wenn er als Parameter für true übergeben wird desc, wird er in absteigender Reihenfolge sortiert. Andernfalls wird in aufsteigender Reihenfolge sortiert.

Jetzt sollten Sie in der Lage sein existingStudents.OrderBy("City",true);oderexistingStudents.OrderBy("City",false);

Ikarus
quelle
10
Diese Antwort ist fantastisch und viel besser als die Reflexionsantwort. Dies funktioniert tatsächlich mit anderen Anbietern wie dem Entity Framework.
Sam
2
Ich würde dies zehnmal abstimmen, wenn ich könnte !!! Wo lernst du solche Erweiterungsmethoden zu schreiben ?? !!
Jach
3
Sollte dies ein IOrderedQueryable zurückgeben, genau wie das eingebaute OrderBy? Auf diese Weise können Sie .ThenBy darauf anrufen.
Patrick Szalapski
4
Dies scheint bei Verwendung von EFCore 3.0 nicht mehr zu funktionieren. Ich erhalte einen Laufzeitfehler, bei dem die Abfrage nicht übersetzt werden kann.
Mildan
3
Ja, @Mildan, das bricht auch für mich in 3.0 und 3.1 ein. mit dem Fehler ~ "kann nicht übersetzen". Ich benutze Pomelo für MySQl, wenn das relevant ist. Das Problem ist der Ausdruck. WENN Sie den Ausdruck von Hand codieren, funktioniert er. Geben Sie also anstelle von Lambda.Expression () Folgendes an: LambdaExpression orderByExp1 = (Ausdruck <Func <AgencySystemBiz, Zeichenfolge >>) (x => x.Name);
Bedrohung
10

Um die Antwort von @Icarus zu erweitern : Wenn der Rückgabetyp der Erweiterungsmethode ein IOrderedQueryable anstelle eines IQueryable sein soll, können Sie das Ergebnis einfach wie folgt umwandeln:

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
quelle
2
Es scheint, dass andere Antworten für Entity Framework nicht geeignet waren. Dies ist eine perfekte Lösung für EF, da Linq to Entities GetProperty, GetValue
Bill
1
Diese Methode scheint für mich in 3.0 und 3.1 fehlgeschlagen zu sein (sie hat in 2.2 funktioniert). Ich benutze Pomelo für MySql, damit dies relevant sein kann. Es gibt eine Lösung, aber es ist hässlich. Siehe meinen Kommentar oben.
Bedrohung
Dies hat bei mir in EF 3.0 funktioniert. Sie sollten jedoch die folgende Zeile ändern, damit das Front-End nicht mit der Groß- und Kleinschreibung übereinstimmen muss: var property = type.GetProperty (OrderByProperty, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
König Arthur der Dritte
Ist dies noch für Core 3.1 optimiert?
Chris Go
8

1) Installieren Sie System.Linq.Dynamic

2) Fügen Sie den folgenden Code hinzu

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) Schreiben Sie Ihren Schalter zur Auswahl der Lambda-Funktion

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) Verwenden Sie Ihre Helfer

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

5) Sie können es mit Pagging verwenden ( 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);
    }

Erläuterung

Mit System.Linq.Dynamic können wir den Zeichenfolgenwert in der OrderBy-Methode festlegen. In dieser Erweiterung wird der String jedoch nach Lambda analysiert. Also dachte ich, es würde funktionieren, wenn wir Lambda in einen String analysieren und ihn der OrderBy-Methode geben würden. Und es funktioniert!

Igor Valikovsky
quelle
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
quelle
Brillant! Genau das, was ich brauchte.
Brandon Griffin
5

Hier ist etwas, das ich mir ausgedacht habe, um mit einem bedingten Abstieg umzugehen. Sie können dies mit anderen Methoden zum keySelectordynamischen Generieren der Funktion kombinieren .

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

Verwendung:

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

Beachten Sie, dass Sie diese .OrderByErweiterung mit einem neuen Parameter auf ein beliebiges IQueryable verketten können.

// 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
quelle
3

Dies lässt Sie nicht bestehen string, wie Sie in Ihrer Frage gefordert haben, aber es könnte trotzdem für Sie funktionieren.

Die OrderByDescendingMethode benötigt a Func<TSource, TKey>, sodass Sie Ihre Funktion folgendermaßen umschreiben können:

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

Es gibt auch andere Überladungen für OrderByDescendinga Expression<Func<TSource, TKey>>und / oder a IComparer<TKey>. Sie können sich auch diese ansehen und herausfinden, ob sie Ihnen etwas Nützliches bieten.

Merlyn Morgan-Graham
quelle
Dies funktioniert nicht, da Sie den Typ des TKey nicht definieren. Sie müssen Ihr <T> ändern, um stattdessen <TKey> zu haben.
Patrick Desjardins
Genau das hat bei mir funktioniert! Ich wollte eine Funktion, die eine Liste aufsteigend oder absteigend anordnet, abhängig von einem übergebenen Bool-Wert. Ihr Code hat mit ein wenig Optimierung großartig funktioniert!
Joe Gayetty
LINQ in Aktion: IEnumerable <Book> CustomSort <TKey> (Func <Book, TKey> -Selektor, Boolescher Aufstieg) {IEnumerable <Book> books = SampleData.Books; aufsteigend zurückkehren? books.OrderBy (Selektor): books.OrderByDescending (Selektor); }
Leszek P
1

Die einzige Lösung, die für mich funktioniert hat, wurde hier veröffentlicht: https://gist.github.com/neoGeneva/1878868 von neoGeneva.

Ich werde seinen Code erneut veröffentlichen, da er gut funktioniert und ich nicht möchte, dass er in den Interwebs verloren geht!

    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;
    }
user1477388
quelle
1
  • Fügen Sie Ihrem Code das Nugget-Paket Dynamite hinzu

  • Fügen Sie den Namespace Dynamite.Extensions hinzu. Beispiel: using Dynamite.Extensions;

  • Geben Sie die Reihenfolge nach Abfrage wie bei jeder SQL-Abfrage an. Beispiel: Students.OrderBy ("City DESC, Address"). ToList ();

Sreeja SJ
quelle
1

Um die Antwort von @Icarus zu erweitern: Wenn Sie nach zwei Feldern sortieren möchten, könnte ich die folgende Funktion ausführen (für ein Feld funktioniert die Antwort von Icarius sehr gut).

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

Dies ist die Funktion, die der Body für den Lambda-Ausdruck zurückgibt. Er funktioniert mit string und int. Es reicht jedoch aus, weitere Typen hinzuzufügen, damit er je nach den Anforderungen der einzelnen Programmierer funktioniert

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

Um es zu verwenden, wird Folgendes getan

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

Wenn es einen besseren Weg gibt, dies zu tun, wäre es großartig, wenn sie ihn teilen

Ich habe es geschafft, es zu lösen dank: Wie kann ich mit Linq einen Lambda-Ausdruck mit mehreren Eigenschaften erstellen?

Ruben Hidalgo
quelle
-1

Ich bin viel zu spät zur Party, aber keine dieser Lösungen hat bei mir funktioniert. Ich wollte unbedingt System.Linq.Dynamic ausprobieren, aber ich konnte das bei Nuget nicht finden, vielleicht abgeschrieben? In jedem Fall...

Hier ist eine Lösung, die ich gefunden habe. Ich musste dynamisch eine Mischung aus OrderBy , OrderByDescending und OrderBy> ThenBy verwenden .

Ich habe einfach eine Erweiterungsmethode für mein Listenobjekt erstellt, ein bisschen hacky, wie ich weiß ... Ich würde dies nicht empfehlen, wenn es etwas wäre, von dem ich viel gemacht habe, aber es ist gut für eine einmalige.

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
quelle