Bagaimana cara menentukan argumen Linq OrderBy secara dinamis?

94

Bagaimana cara menentukan argumen yang diteruskan orderbymenggunakan nilai yang saya ambil sebagai parameter?

Ex:

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

Implementasi saat ini:

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

Alih-alih c.Address, bagaimana saya bisa menganggapnya sebagai parameter?

Contoh

 string param = "City";
 List<Student> orderbyAddress = existingStudends.OrderByDescending(c => param).ToList();
Sreedhar
sumber
4
Anda mungkin mencari Linq Dinamis: weblogs.asp.net/scottgu/archive/2008/01/07/…
BrokenGlass
@Nev_Rahd: Mencoba mengklarifikasi pertanyaan sedikit. Juga, OrderByadalah fitur Linq, dan aktif IEnumerable, bukan fitur khusus List. Jangan ragu untuk memutar kembali hasil edit atau mengubahnya lebih lanjut :)
Merlyn Morgan-Graham
Kemungkinan duplikat Dynamic LINQ OrderBy di IEnumerable <T>
Michael Freidgeim

Jawaban:

129

Berikut kemungkinan menggunakan refleksi ...

var param = "Address";    
var propertyInfo = typeof(Student).GetProperty(param);    
var orderByAddress = items.OrderBy(x => propertyInfo.GetValue(x, null));
codeConcussion
sumber
3
Tetapi apakah benar ketika berbicara tentang ekspresi Linq yang ditafsirkan oleh penyedia, seperti Entity Framework (sql server, atau lainnya) ??
Boussema
2
@vijay - gunakan ThenBymetode ini .
codeConcussion
7
Ketika saya mencoba ini saya mendapatkan kesalahan: LINQ ke Entitas tidak mengenali metode metode 'System.Object GetValue (System.Object, System.Object [])', dan metode ini tidak dapat diterjemahkan ke dalam ekspresi toko. Apakah jawaban ini hanya berlaku untuk Linq ke SQL?
Philreed
4
Tidak ada kesalahan dengan .AsEnumerable (): var orderByAddress = items.AsEnumerable (). OrderBy (x => propertyInfo.GetValue (x, null));
Kaisar
1
Bagaimana saya bisa secara dinamis memutuskan untuk memesan dengan asc atau desc
Hitesh Modha
124

Anda dapat menggunakan sedikit refleksi untuk membuat pohon ekspresi sebagai berikut (ini adalah metode ekstensi):

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 adalah nama Properti yang ingin Anda pesan dan jika parameternya lulus true desc , akan mengurutkan dalam urutan menurun; jika tidak, akan mengurutkan dalam urutan menaik.

Sekarang Anda harus bisa melakukan existingStudents.OrderBy("City",true);atauexistingStudents.OrderBy("City",false);

Icarus
sumber
10
Jawaban ini luar biasa, dan jauh lebih baik daripada jawaban refleksi. Ini sebenarnya berfungsi dengan penyedia lain seperti kerangka entitas.
Sam
2
Aku akan memilih ini sepuluh kali jika aku bisa !!! Di mana Anda belajar menulis metode ekstensi seperti ini ?? !!
Jach
3
Haruskah ini mengembalikan IOrderedQueryable, seperti halnya OrderBy bawaan? Dengan begitu, Anda bisa menelepon. KemudianBy di atasnya.
Patrick Szalapski
4
Ini sepertinya tidak lagi berfungsi saat menggunakan EFCore 3.0, saya mendapatkan kesalahan runtime yang tidak dapat menerjemahkan kueri.
Mildan
3
Ya, @Mildan, Ini juga masuk ke 3.0 dan 3.1 untuk saya. dengan kesalahan ~ "tidak bisa menerjemahkan". Saya menggunakan Pomelo untuk MySQl jika itu relevan. Masalahnya adalah Ekspresi. JIKA Anda memberi kode tangan, ekspresi itu berfungsi. Jadi, alih-alih Lambda.Expression () cukup berikan sesuatu seperti: LambdaExpression orderByExp1 = (Expression <Func <AgencySystemBiz, string >>) (x => x.Name);
Ancaman
9

Untuk memperluas jawaban oleh @Icarus : jika Anda ingin tipe kembalian dari metode ekstensi menjadi IOrderedQueryable, bukan IQuerable, Anda cukup menampilkan hasilnya sebagai berikut:

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
sumber
2
Tampaknya jawaban lain tidak sesuai untuk Entity Framework. Ini adalah solusi sempurna untuk EF karena Linq ke Entitas tidak mendukung GetProperty, GetValue
Bill
1
Metode ini tampaknya gagal untuk saya di 3.0 dan 3.1 (berfungsi di 2.2). Saya menggunakan Pomelo untuk MySql jadi itu mungkin relevan. Ada pekerjaan di sekitar tapi jelek. Lihat komentar saya di atas.
Menace
Ini berhasil untuk saya di EF 3.0. Namun, Anda harus mengubah baris berikut agar front-end tidak perlu mencocokkan sensitivitas huruf: var property = type.GetProperty (OrderByProperty, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
Raja Arthur Ketiga
Apakah ini masih dioptimalkan untuk Core 3.1?
Chris Go
8

1) Instal System.Linq.Dynamic

2) Tambahkan kode berikut

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) Tulis tombol Anda untuk memilih fungsi 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) Gunakan pembantu Anda

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

5) Anda dapat menggunakannya dengan 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);
    }

Penjelasan

System.Linq.Dynamic memungkinkan kita untuk mengatur nilai string dalam metode OrderBy. Tetapi di dalam ekstensi ini, string akan diurai ke Lambda. Jadi saya pikir itu akan berhasil jika kita akan mengurai Lambda menjadi string dan memberikannya ke metode OrderBy. Dan berhasil!

Igor Valikovsky
sumber
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
sumber
Cemerlang! Persis yang saya butuhkan.
Brandon Griffin
5

Berikut adalah sesuatu yang saya temukan untuk menangani Descending bersyarat. Anda dapat menggabungkan ini dengan metode lain untuk menghasilkan keySelectorfungsi secara dinamis.

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

Pemakaian:

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

Perhatikan bahwa ini memungkinkan Anda untuk merantai .OrderByekstensi ini dengan parameter baru ke IQuerable.

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

Hal ini tidak memungkinkan Anda lulus string, seperti yang Anda minta dalam pertanyaan, tetapi mungkin masih berhasil untuk Anda.

The OrderByDescendingMetode mengambil Func<TSource, TKey>, sehingga Anda dapat menulis ulang fungsi Anda dengan cara ini:

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

Ada kelebihan lain untuk OrderByDescendingjuga yang mengambil a Expression<Func<TSource, TKey>>, dan / atau a IComparer<TKey>. Anda juga bisa melihat ke dalamnya dan melihat apakah mereka memberi Anda sesuatu yang berguna.

Merlyn Morgan-Graham
sumber
Ini tidak berfungsi karena Anda tidak menentukan tipe TKey. Anda harus mengubah <T> Anda untuk memiliki <TKey> sebagai gantinya.
Patrick Desjardins
Inilah yang berhasil untuk saya! Saya menginginkan sebuah fungsi yang akan mengurutkan list ascending, atau descending, bergantung pada nilai bool yang diteruskan. Kode Anda bekerja dengan baik dengan sedikit penyesuaian!
Joe Gayetty
LINQ dalam Tindakan: IEnumerable <Book> CustomSort <TKey> (Func <Book, TKey> selector, Boolean ascending) {IEnumerable <Book> books = SampleData.Books; kembali naik? books.OrderBy (selector): books.OrderByDescending (selector); }
Leszek P
1

Satu-satunya solusi yang berhasil untuk saya diposting di sini https://gist.github.com/neoGeneva/1878868 oleh neoGeneva.

Saya akan memposting ulang kodenya karena berfungsi dengan baik dan saya tidak ingin kode itu hilang di interwebs!

    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;
    }
pengguna1477388
sumber
1
  • Tambahkan paket nugget Dynamite ke kode Anda

  • Tambahkan namespace Dynamite.Extensions Misalnya: using Dynamite.Extensions;

  • Berikan Urutan berdasarkan kueri seperti kueri SQL apa pun Misalnya: students.OrderBy ("City DESC, Address"). ToList ();

Sreeja SJ
sumber
1

Untuk memperluas respon @Icarus: jika Anda ingin mengurutkan berdasarkan dua bidang saya dapat melakukan fungsi berikut (untuk satu bidang respon Icarius bekerja dengan sangat baik).

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

Ini adalah fungsi yang dikembalikan tubuh untuk ekspresi lambda, ini berfungsi dengan string dan int, tetapi cukup untuk menambahkan lebih banyak tipe agar berfungsi sesuai dengan kebutuhan setiap programmer

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

untuk menggunakannya, berikut ini dilakukan

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

jika ada cara yang lebih baik untuk melakukan ini, alangkah baiknya jika mereka membagikannya

Saya berhasil menyelesaikannya berkat: Bagaimana cara membuat ekspresi lambda properti berganda dengan LINQ

Ruben Hidalgo
sumber
-2

Saya terlambat ke pesta tetapi tidak ada solusi yang berhasil untuk saya. Saya sangat ingin mencoba System.Linq.Dynamic, tetapi saya tidak dapat menemukannya di Nuget, mungkin terdepresiasi? Bagaimanapun juga ...

Berikut adalah solusi yang saya dapatkan. Saya perlu secara dinamis menggunakan campuran OrderBy , OrderByDescending dan OrderBy> ThenBy .

Saya hanya membuat metode ekstensi untuk objek daftar saya, sedikit hacky saya tahu ... Saya tidak akan merekomendasikan ini jika itu adalah sesuatu yang sering saya lakukan, tetapi bagus untuk satu kali.

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
sumber