ฉันจะระบุอาร์กิวเมนต์ Linq OrderBy แบบไดนามิกได้อย่างไร


95

ฉันจะระบุอาร์กิวเมนต์ที่ส่งไปยัง orderbyโดยใช้ค่าที่ฉันใช้เป็นพารามิเตอร์ได้อย่างไร

เช่น:

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

การใช้งานในปัจจุบัน:

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

แทน c.Addressว่าฉันสามารถใช้เวลาที่เป็นพารามิเตอร์?

ตัวอย่าง

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

4
คุณอาจกำลังมองหา Dynamic Linq: weblogs.asp.net/scottgu/archive/2008/01/07/…
BrokenGlass

@Nev_Rahd: พยายามชี้แจงคำถามเล็กน้อย นอกจากนี้OrderByเป็นคุณลักษณะ Linq และอยู่บนไม่คุณลักษณะเฉพาะเพื่อIEnumerable Listอย่าลังเลที่จะย้อนกลับการแก้ไขหรือเปลี่ยนแปลงเพิ่มเติม :)
Merlyn Morgan-Graham

อาจซ้ำกันได้ของDynamic LINQ OrderBy บน IEnumerable <T>
Michael Freidgeim

คำตอบ:


131

นี่คือความเป็นไปได้โดยใช้การสะท้อน ...

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

3
แต่มันเป็นความจริงหรือไม่เมื่อพูดถึงนิพจน์ Linq ที่ตีความโดยผู้ให้บริการเช่น Entity Framework (เซิร์ฟเวอร์ sql หรืออื่น ๆ ) ??
a.boussema

2
@vijay - ใช้วิธีการThenBy
codeConcussion

9
เมื่อฉันลองสิ่งนี้ฉันได้รับข้อผิดพลาด: LINQ to Entities ไม่รู้จักเมธอด 'System.Object GetValue (System.Object, System.Object [])' และวิธีนี้ไม่สามารถแปลเป็นนิพจน์ร้านค้าได้ คำตอบนี้ใช้ได้กับ Linq To SQL เท่านั้นหรือไม่
Philreed

4
ไม่มีข้อผิดพลาดกับ. AsEnumerable (): var orderByAddress = items. AsEnumerable (). OrderBy (x => propertyInfo.GetValue (x, null));
Caesar

1
ฉันจะตัดสินใจสั่งโดย asc หรือ desc แบบไดนามิกได้อย่างไร
Hitesh Modha

128

คุณสามารถใช้การสะท้อนเล็กน้อยเพื่อสร้างแผนผังนิพจน์ได้ดังนี้ (นี่คือวิธีการขยาย):

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คือชื่อคุณสมบัติที่คุณต้องการเรียงลำดับและถ้าผ่านจริงเป็นพารามิเตอร์สำหรับdescจะเรียงลำดับจากมากไปหาน้อย มิฉะนั้นจะเรียงลำดับจากน้อยไปมาก

ตอนนี้คุณควรจะทำได้existingStudents.OrderBy("City",true);หรือexistingStudents.OrderBy("City",false);


10
คำตอบนี้ยอดเยี่ยมและดีกว่าคำตอบแบบสะท้อน สิ่งนี้ใช้ได้จริงกับผู้ให้บริการรายอื่นเช่นกรอบงานเอนทิตี
แซม

2
ฉันจะโหวตให้สิบครั้งถ้าทำได้ !!! คุณเรียนรู้วิธีการเขียนนามสกุลแบบนี้ได้ที่ไหน ?? !!
Jach

3
สิ่งนี้ควรส่งคืน IOrderQueryable เช่นเดียวกับ OrderBy ในตัวหรือไม่ ด้วยวิธีนี้คุณสามารถโทรหาได้จากนั้น
Patrick Szalapski

4
ดูเหมือนว่าจะใช้ไม่ได้อีกต่อไปเมื่อใช้ EFCore 3.0 ฉันได้รับข้อผิดพลาดรันไทม์ซึ่งไม่สามารถแปลข้อความค้นหาได้
Mildan

3
ใช่ @Mildan สิ่งนี้แบ่งใน 3.0 และ 3.1 สำหรับฉันเช่นกัน ด้วยข้อผิดพลาด ~ "แปลไม่ได้" ฉันใช้ Pomelo สำหรับ MySQl หากเกี่ยวข้อง ปัญหาคือนิพจน์ หากคุณเขียนโค้ดด้วยมือนิพจน์จะใช้งานได้ ดังนั้นแทนที่จะเป็น Lambda.Expression () เพียงแค่ให้สิ่งที่ต้องการ: LambdaExpression orderByExp1 = (Expression <Func <AgencySystemBiz, string >>) (x => x.Name);
อันตราย

9

หากต้องการขยายคำตอบโดย @Icarus : หากคุณต้องการให้ประเภทการส่งคืนของวิธีการขยายเป็น IOrderQueryable แทนที่จะเป็น IQueryable คุณสามารถส่งผลลัพธ์ได้ดังนี้:

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
ดูเหมือนว่าคำตอบอื่น ๆ ไม่เหมาะสมสำหรับ Entity Framework นี่เป็นโซลูชันที่สมบูรณ์แบบสำหรับ EF เนื่องจาก Linq to Entities ไม่รองรับ GetProperty, GetValue
Bill

1
วิธีนี้ดูเหมือนจะล้มเหลวสำหรับฉันใน 3.0 และ 3.1 (ใช้ได้ใน 2.2) ฉันใช้ Pomelo สำหรับ MySql ดังนั้นอาจเกี่ยวข้อง มีการทำงานรอบ ๆ แต่มันน่าเกลียด ดูความคิดเห็นของฉันด้านบน
อันตราย

สิ่งนี้ใช้ได้ผลกับฉันใน EF 3.0 อย่างไรก็ตามคุณควรเปลี่ยนบรรทัดต่อไปนี้เพื่อให้ส่วนหน้าไม่จำเป็นต้องตรงกับตัวพิมพ์เล็กและใหญ่: var property = type.GetProperty (OrderByProperty, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
King Arthur the Third

สิ่งนี้ยังเหมาะสำหรับ Core 3.1 หรือไม่
Chris Go

8

1) ติดตั้งSystem.Linq.Dynamic

2) เพิ่มรหัสต่อไปนี้

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

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) ใช้ตัวช่วยของคุณ

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

5) คุณสามารถใช้กับการแบ่งหน้า ( 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);
    }

คำอธิบาย

System.Linq.Dynamic ช่วยให้เรากำหนดค่าสตริงในเมธอด OrderBy แต่ภายในส่วนขยายนี้สตริงจะถูกแยกวิเคราะห์เป็นแลมด้า ดังนั้นฉันคิดว่ามันจะได้ผลถ้าเราจะแยกวิเคราะห์ Lambda เป็นสตริงและให้เป็นวิธี OrderBy และได้ผล!


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

ยอดเยี่ยม! สิ่งที่ฉันต้องการ
Brandon Griffin

5

นี่คือสิ่งที่ฉันคิดขึ้นเพื่อจัดการกับ Descending ตามเงื่อนไข คุณสามารถรวมสิ่งนี้กับวิธีการอื่น ๆ ในการสร้างkeySelectorfunc แบบไดนามิก

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

การใช้งาน:

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

สังเกตว่าสิ่งนี้ช่วยให้คุณสามารถเชื่อมโยง.OrderByส่วนขยายนี้กับพารามิเตอร์ใหม่เข้ากับ 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

สิ่งนี้ไม่ให้คุณผ่านไฟล์ stringอย่างที่คุณถามในคำถามของคุณ แต่อาจยังใช้ได้สำหรับคุณ

OrderByDescendingวิธีการใช้เวลาFunc<TSource, TKey>เพื่อให้คุณสามารถเขียนการทำงานของคุณด้วยวิธีนี้:

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

มีการโอเวอร์โหลดอื่น ๆOrderByDescendingเช่นกันที่ใช้ a Expression<Func<TSource, TKey>>และ / หรือ a IComparer<TKey>. นอกจากนี้คุณยังสามารถตรวจสอบสิ่งเหล่านี้และดูว่ามีประโยชน์อะไรหรือไม่


สิ่งนี้ไม่ได้ผลเนื่องจากคุณไม่ได้กำหนดประเภทของ TKey คุณต้องเปลี่ยน <T> ของคุณให้มี <TKey> แทน
Patrick Desjardins

นี่เป็นเพียงสิ่งที่ใช้ได้ผลสำหรับฉัน! ฉันต้องการฟังก์ชั่นที่จะเรียงลำดับรายการจากน้อยไปมากหรือมากไปหาน้อยขึ้นอยู่กับค่าบูลที่ส่งผ่าน รหัสของคุณใช้งานได้ดีด้วยการปรับแต่งเล็กน้อย!
Joe Gayetty

LINQ ในการดำเนินการ: IEnumerable <Book> CustomSort <TKey> (Func <Book, TKey> selector, Boolean ascending) {IEnumerable <Book> books = SampleData.Books; กลับจากน้อยไปมาก? books.OrderBy (ตัวเลือก): books.OrderByDescending (ตัวเลือก); }
Leszek P

1

ทางออกเดียวที่ใช้ได้ผลสำหรับฉันถูกโพสต์ไว้ที่นี่https://gist.github.com/neoGeneva/1878868โดย neoGeneva

ฉันจะโพสต์รหัสของเขาอีกครั้งเพราะมันใช้งานได้ดีและฉันก็ไม่อยากให้มันหายไปใน 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;
    }

1
  • เพิ่มแพ็คเกจนักเก็ตDynamiteลงในโค้ดของคุณ

  • เพิ่มเนมสเปซDynamite.Extensions เช่น: using Dynamite.Extensions;

  • ให้คำสั่งตามแบบสอบถามเช่นแบบสอบถาม SQL ใด ๆ เช่นนักเรียน.OrderBy ("เมือง DESC ที่อยู่") ToList ();


1

หากต้องการขยายการตอบกลับของ @Icarus: หากคุณต้องการเรียงลำดับตามสองฟิลด์ฉันสามารถใช้ฟังก์ชันต่อไปนี้ได้ (สำหรับฟิลด์เดียวการตอบสนองของ Icarius จะทำงานได้ดีมาก)

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

นี่คือฟังก์ชั่นที่ร่างกายส่งกลับมาสำหรับนิพจน์แลมบ์ดามันทำงานร่วมกับสตริงและ int แต่ก็เพียงพอที่จะเพิ่มประเภทอื่น ๆ เพื่อให้มันทำงานได้ตามความต้องการของโปรแกรมเมอร์แต่ละคน

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

เพื่อใช้งานดังต่อไปนี้เสร็จสิ้น

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

ถ้ามีวิธีที่ดีกว่านี้ก็จะดีมากถ้าพวกเขาแบ่งปัน

ฉันจัดการเพื่อแก้ปัญหานี้ได้อย่างไรฉันจะสร้างนิพจน์แลมด้าคุณสมบัติหลายรายการด้วย Linq ได้อย่างไร


-2

ฉันไปงานปาร์ตี้สายไปแล้ว แต่วิธีแก้ปัญหาเหล่านี้ไม่ได้ผลสำหรับฉัน ฉันกระตือรือร้นที่จะลอง System.Linq.Dynamic แต่ฉันไม่พบสิ่งนั้นใน Nuget อาจจะคิดค่าเสื่อมราคา? ไม่ว่าจะด้วยวิธีใดก็ตาม ...

นี่คือวิธีแก้ปัญหาที่ฉันคิดขึ้นมา ฉันต้องการที่จะใช้แบบไดนามิกที่มีส่วนผสมของOrderBy , OrderByDescendingและOrderBy> ThenBy

ฉันเพิ่งสร้างวิธีการขยายสำหรับวัตถุในรายการของฉันฉันรู้ว่าแฮ็คนิดหน่อย ... ฉันจะไม่แนะนำสิ่งนี้หากเป็นสิ่งที่ฉันกำลังทำอยู่เป็นจำนวนมาก แต่ก็ดีสำหรับการปิดครั้งเดียว

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);
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.