Linq OrderBy bağımsız değişkenini dinamik olarak nasıl belirtebilirim?


95

orderbyParametre olarak aldığım bir değeri kullanarak iletilen bağımsız değişkeni nasıl belirtebilirim ?

Ör:

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

Şu anda uygulama:

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

Bunun yerine, c.Addressbunu bir parametre olarak nasıl alabilirim?

Misal

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

4
Dynamic Linq'i
BrokenGlass

@Nev_Rahd: Soruyu biraz açıklığa kavuşturmaya çalıştım. Ayrıca, OrderBybir Linq özelliğidir ve açıktır, IEnumerablebelirli bir özellik değildir List. Düzenlemeyi geri
almaktan

Yanıtlar:


129

İşte yansıma kullanarak bir olasılık ...

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

3
Ancak Entity Framework (sql server veya diğer) gibi sağlayıcılar tarafından yorumlanan Linq ifadeleri söz konusu olduğunda doğru mu?
a.boussema

2
@vijay - ThenByyöntemi kullanın .
codeConcussion

9
Bunu denediğimde şu hatayı alıyorum: LINQ to Entities, 'System.Object GetValue (System.Object, System.Object [])' yöntemini tanımıyor ve bu yöntem bir mağaza ifadesine çevrilemiyor. Bu cevap sadece Linq To SQL için mi geçerli?
2014

4
.AsEnumerable () ile hata yok: var orderByAddress = items.AsEnumerable (). OrderBy (x => propertyInfo.GetValue (x, null));
Caesar

1
Artan veya azalan sıralamaya göre dinamik olarak nasıl karar verebilirim
Hitesh Modha

127

İfade ağacını aşağıdaki gibi oluşturmak için biraz yansıma kullanabilirsiniz (bu bir genişletme yöntemidir):

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

orderByPropertysıralamak istediğiniz Özellik adıdır ve için parametre olarak true desciletilirse, azalan düzende sıralanır; aksi takdirde artan düzende sıralanır.

Şimdi yapabilmelisin existingStudents.OrderBy("City",true);veyaexistingStudents.OrderBy("City",false);


10
Bu cevap harika ve yansıma cevabından çok daha iyi. Bu aslında varlık çerçevesi gibi diğer sağlayıcılarla çalışır.
Sam

2
Yapabilseydim bunu on kez yükseltirdim !!! Böyle bir uzatma yöntemi yazmayı nereden öğreniyorsunuz ?? !!
Jach

3
Bu, yerleşik OrderBy gibi bir IOrderedQueryable döndürmeli mi? Bu şekilde, .ThenBy'yi arayabilirsiniz.
Patrick Szalapski

4
Bu EFCore 3.0 kullanırken artık çalışmıyor gibi görünüyor, sorguyu çeviremediği bir çalışma zamanı hatası alıyorum.
Mildan

3
Evet @ Mildan, Bu benim için de 3.0 ve 3.1'de kırılıyor. ~ "cant translate" hatasıyla. İlgili ise MySQl için Pomelo kullanıyorum. Sorun İfade. If you hand code el kodu, çalıştığı ifade. Bu nedenle, Lambda.Expression () yerine şuna benzer bir şey sağlayın: LambdaExpression orderByExp1 = (Expression <Func <AgencySystemBiz, string >>) (x => x.Name);
Tehdit

9

Yanıtı @Icarus ile genişletmek için : Uzantı yönteminin dönüş türünün bir IQueryable yerine bir IOrderedQueryable olmasını istiyorsanız, sonucu aşağıdaki gibi dönüştürebilirsiniz:

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
Diğer yanıtların Entity Framework için uygun olmadığı görülüyor. Linq to Entities, GetProperty'yi desteklemediği için bu EF için mükemmel bir çözümdür, GetValue
Bill

1
Bu yöntem benim için 3.0 ve 3.1'de başarısız görünüyor (2.2'de çalıştı). MySql için Pomelo kullanıyorum, böylece alakalı olabilir. Etrafta bir iş var ama çirkin. Yukarıdaki yorumuma bakın.
Tehdit

Bu benim için EF 3.0'da çalıştı. Ancak, ön ucun büyük / küçük harfe duyarlılıkla eşleşmesi gerekmeyecek şekilde aşağıdaki satırı değiştirmelisiniz: var property = type.GetProperty (OrderByProperty, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
Kral Arthur Üçüncü

Bu hala Core 3.1 için optimize edilmiş mi?
Chris Go

8

1) System.Linq.Dynamic'i yükleyin

2) Aşağıdaki kodu ekleyin

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 işlevini seçmek için anahtarınızı yazın

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) Yardımcılarınızı kullanın

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

5) Sayfalandırma ile kullanabilirsiniz ( 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);
    }

Açıklama

System.Linq.Dynamic, OrderBy yönteminde dize değerini ayarlamamıza izin verir. Ancak bu uzantının içinde dize Lambda'ya ayrıştırılacaktır. Bu yüzden Lambda'yı dizeye ayrıştırıp OrderBy yöntemine verirsek işe yarayacağını düşündüm. Ve çalışıyor!


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

Parlak! Tam olarak ihtiyacım olan şey.
Brandon Griffin

5

Koşullu Alçalma ile başa çıkmak için bulduğum bir şey var. Bunu, keySelectorişlevi dinamik olarak oluşturmanın diğer yöntemleriyle birleştirebilirsiniz .

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

Kullanım:

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

Bunun, bu .OrderByuzantıyı yeni bir parametreyle herhangi bir IQueryable'a zincirlemenize izin verdiğine dikkat edin .

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

Bu, stringsorunuzda sorduğunuz gibi a geçmenize izin vermez , ancak yine de işinize yarayabilir.

OrderByDescendingYöntem alır Func<TSource, TKey>, böylece fonksiyonu bu şekilde yazabilirsiniz:

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

OrderByDescendingA Expression<Func<TSource, TKey>>, ve / veya a alan başka aşırı yüklemeler de vardır IComparer<TKey>. Bunlara da bakabilir ve size yararlı bir şey sağlayıp sağlamadıklarını görebilirsiniz.


TKey türünü tanımlamadığınız için bu çalışmaz. Bunun yerine <T> anahtarınızı <TKey> olacak şekilde değiştirmeniz gerekir.
Patrick Desjardins

Bu benim için işe yarayan şeydi! İletilen bool değerine bağlı olarak artan veya azalan bir liste sıralayacak bir işlev istedim. Kodunuz biraz ince ayar yaparak harika çalıştı!
Joe Gayetty

LINQ İş Başında: IEnumerable <Book> CustomSort <TKey> (Func <Book, TKey> seçici, Boolean artan) {IEnumerable <Book> books = SampleData.Books; artan geri dönüş? books.OrderBy (seçici): books.OrderByDescending (seçici); }
Leszek P

1

Benim için işe yarayan tek çözüm burada neoGeneva tarafından https://gist.github.com/neoGeneva/1878868 yayınlandı .

Kodunu tekrar göndereceğim çünkü iyi çalışıyor ve interweblerde kaybolmasını istemem!

    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 nugget paketini kodunuza ekleyin

  • Dynamite.Extensions ad alanını ekleyin. Örneğin: Dynamite.Extensions kullanma;

  • Herhangi bir SQL sorgusu gibi sorguya göre Sıralama verin Ör: öğrenciler.OrderBy ("Şehir DESC, Adres"). ToList ();


1

@Icarus'un yanıtını genişletmek için: iki alana göre sıralamak isterseniz, aşağıdaki işlevi gerçekleştirebilirim (bir alan için Icarius'un yanıtı çok iyi çalışıyor).

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

Bu, lambda ifadesi için gövdenin döndürdüğü işlevdir, string ve int ile çalışır, ancak her programcının ihtiyacına göre çalışmasını sağlamak için daha fazla tür eklemek yeterlidir.

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

kullanmak için aşağıdakiler yapılır

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

Bunu yapmanın daha iyi bir yolu varsa, paylaşırlarsa harika olur

Bunu şunun sayesinde çözmeyi başardım: Linq ile nasıl Çoklu özellik lambda ifadesi yapabilirim


-2

Partiye çok geç kaldım ama bu çözümlerin hiçbiri benim için işe yaramadı. System.Linq.Dynamic'i denemeye hevesliydim, ancak bunu Nuget'te bulamadım, belki değer kaybetmiş olabilir mi? Öyle ya da böyle...

İşte bulduğum bir çözüm. OrderBy , OrderByDescending ve OrderBy> ThenBy'nin bir karışımını dinamik olarak kullanmam gerekiyordu .

Sadece liste nesnem için bir uzatma yöntemi oluşturdum, biraz hilekar biliyorum ... Çok fazla yaptığım bir şey olsaydı bunu tavsiye etmem, ama bir defaya mahsus iyidir.

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);
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.