Làm cách nào để chỉ định động đối số Linq OrderBy?


94

Làm cách nào để chỉ định đối số được truyền vào orderbybằng cách sử dụng giá trị tôi lấy làm tham số?

Ví dụ:

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

Hiện đang thực hiện:

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

Thay vì c.Address, làm cách nào tôi có thể lấy đó làm tham số?

Thí dụ

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

4
Bạn có thể tìm kiếm động LINQ: weblogs.asp.net/scottgu/archive/2008/01/07/...
BrokenGlass

@Nev_Rahd: Đã cố gắng làm rõ câu hỏi một chút. Ngoài ra, OrderBylà một tính năng Linq và đang bật IEnumerable, không phải là một tính năng dành riêng cho List. Cảm thấy tự do để cuộn chỉnh sửa lại hoặc thay đổi nó nữa :)
Merlyn Morgan-Graham

Câu trả lời:


128

Đây là một khả năng sử dụng phản chiếu ...

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

3
Nhưng liệu nó có đúng khi nói đến các biểu thức Linq được các nhà cung cấp dịch, như Entity Framework (máy chủ sql hoặc khác) ??
a.boussema

2
@vijay - sử dụng ThenByphương pháp này .
codeConcussion

7
Khi tôi thử điều này, tôi gặp lỗi: LINQ to Entities không nhận dạng được phương thức 'System.Object GetValue (System.Object, System.Object [])' và phương thức này không thể được dịch thành một biểu thức lưu trữ. Câu trả lời này chỉ áp dụng cho Linq To SQL?
philreed

4
Không có lỗi với .AsEnumerable (): var orderByAddress = items.AsEnumerable (). OrderBy (x => propertyInfo.GetValue (x, null));
Caesar

1
Làm thế nào tôi có thể tự động quyết định trật tự bởi asc hoặc desc
Hitesh Modha

122

Bạn có thể sử dụng một chút phản xạ để xây dựng cây biểu thức như sau (đây là một phương thức mở rộng):

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

orderByPropertylà tên Thuộc tính bạn muốn sắp xếp theo thứ tự và nếu truyền true làm tham số cho desc, sẽ sắp xếp theo thứ tự giảm dần; nếu không, sẽ sắp xếp theo thứ tự tăng dần.

Bây giờ bạn có thể làm existingStudents.OrderBy("City",true);hoặcexistingStudents.OrderBy("City",false);


10
Câu trả lời này thật tuyệt vời và tốt hơn nhiều so với câu trả lời phản ánh. Điều này thực sự hoạt động với các nhà cung cấp khác như khung thực thể.
Sam

2
Tôi sẽ bỏ phiếu mười lần nếu tôi có thể !!! Bạn học cách viết phần mở rộng như thế này ở đâu ?? !!
Jach

3
Điều này có nên trả về IOrderedQueryable, giống như OrderBy tích hợp sẵn không? Bằng cách đó, bạn có thể gọi. Sau đó, nhờ vào nó.
Patrick Szalapski

4
Điều này dường như không còn hoạt động khi sử dụng EFCore 3.0, tôi đang gặp lỗi thời gian chạy trong đó nó không thể dịch truy vấn.
Mildan

3
Vâng, @Mildan, Điều này cũng phá vỡ 3.0 và 3.1 đối với tôi. với lỗi ~ "không thể dịch". Tôi sử dụng Pomelo cho MySQl nếu điều đó có liên quan. Vấn đề là Biểu hiện. NẾU bạn viết tay biểu thức nó hoạt động. Vì vậy, thay vì Lambda.Expression () chỉ cần cung cấp một cái gì đó như: LambdaExpression orderByExp1 = (Expression <Func <AgencySystemBiz, string >>) (x => x.Name);
Mối đe dọa vào

10

Để mở rộng câu trả lời của @Icarus : nếu bạn muốn kiểu trả về của phương thức mở rộng là IOrderedQueryable thay vì IQueryable, bạn có thể chỉ cần truyền kết quả như sau:

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
Có vẻ như các câu trả lời khác không phù hợp với Entity Framework. Đây là một giải pháp hoàn hảo cho EF vì Linq to Entities không hỗ trợ GetProperty, GetValue
Bill

1
Phương pháp này dường như không thành công đối với tôi trong 3.0 và 3.1 (nó hoạt động trong 2.2). Tôi sử dụng Pomelo cho MySql để điều đó có thể phù hợp. Có một công việc xung quanh nhưng xấu xí của nó. Xem bình luận của tôi ở trên.
Mối đe dọa

Điều này đã làm việc cho tôi trong EF 3.0. Tuy nhiên, bạn nên thay đổi dòng sau để giao diện người dùng không cần phải khớp với phân biệt chữ hoa chữ thường: var property = type.GetProperty (OrderByProperty, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
Vua Arthur Đệ Tam vào

Điều này vẫn được tối ưu hóa cho Core 3.1?
Chris Go

8

1) Cài đặt System.Linq.Dynamic

2) Thêm mã sau

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) Viết công tắc của bạn để chọn chức năng 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) Sử dụng người trợ giúp của bạn

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

5) Bạn có thể sử dụng nó với tính năng phân trang ( 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);
    }

Giải trình

System.Linq.Dynamic cho phép chúng ta thiết lập giá trị chuỗi trong phương thức OrderBy. Nhưng bên trong phần mở rộng này, chuỗi sẽ được phân tích cú pháp thành Lambda. Vì vậy, tôi nghĩ nó sẽ hoạt động nếu chúng ta phân tích cú pháp Lambda thành chuỗi và đưa nó cho phương thức OrderBy. Và nó hoạt động!


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

Xuất sắc! Chính xác những gì tôi cần.
Brandon Griffin

5

Đây là điều mà tôi đã nghĩ ra để đối phó với Giảm dần có điều kiện. Bạn có thể kết hợp điều này với các phương pháp tạo keySelectorfunc động khác.

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

Sử dụng:

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

Lưu ý rằng điều này cho phép bạn liên kết .OrderByphần mở rộng này với một tham số mới vào bất kỳ IQueryable nào.

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

Điều này không cho phép bạn vượt qua a string, như bạn đã yêu cầu trong câu hỏi của mình, nhưng nó vẫn có thể hiệu quả với bạn.

Các OrderByDescendingphương pháp có một Func<TSource, TKey>, vì vậy bạn có thể viết lại chức năng của bạn theo cách này:

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

Cũng có những trường hợp quá tải khác OrderByDescendingmất a Expression<Func<TSource, TKey>>, và / hoặc a IComparer<TKey>. Bạn cũng có thể xem xét những thứ đó và xem liệu chúng có cung cấp cho bạn bất cứ thứ gì hữu dụng hay không.


Điều này không hoạt động bởi vì bạn không xác định loại TKey. Bạn phải thay đổi <T> của mình để có <TKey>.
Patrick Desjardins

Đây chỉ là những gì làm việc cho tôi! Tôi muốn một hàm sắp xếp thứ tự danh sách tăng dần hoặc giảm dần, tùy thuộc vào giá trị bool được truyền. Mã của bạn hoạt động tốt với một chút điều chỉnh!
Joe Gayetty 21/09/16

LINQ đang thực hiện: IEnumerable <Book> CustomSort <TKey> (Func <Book, TKey> selector, Boolean ascending) {IEnumerable <Book> books = SampleData.Books; trở lại tăng dần? books.OrderBy (bộ chọn): books.OrderByDescending (bộ chọn); }
Leszek P

1

Giải pháp duy nhất phù hợp với tôi đã được đăng ở đây https://gist.github.com/neoGeneva/1878868 bởi neoGeneva.

Tôi sẽ đăng lại mã của anh ấy vì nó hoạt động tốt và tôi không muốn nó bị mất trong 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
  • Thêm gói nugget Dynamite vào mã của bạn

  • Thêm không gian tên Dynamite.Extensions Ví dụ: using Dynamite.Extensions;

  • Đưa ra Thứ tự theo truy vấn giống như bất kỳ truy vấn SQL nào Ví dụ: student.OrderBy ("City DESC, Address"). ToList ();


1

Để mở rộng phản hồi của @Icarus: nếu bạn muốn sắp xếp theo hai trường, tôi có thể thực hiện chức năng sau (đối với một trường, phản hồi của Icarius hoạt động rất tốt).

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

Đây là hàm mà phần thân trả về cho biểu thức lambda, nó hoạt động với string và int, nhưng cũng đủ để thêm nhiều kiểu khác để nó hoạt động theo nhu cầu của từng lập trình viên

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

để sử dụng nó như sau là xong

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

nếu có một cách tốt hơn để làm điều này, sẽ thật tuyệt nếu họ chia sẻ nó

Tôi đã giải quyết được nó nhờ: Làm cách nào để tạo biểu thức lambda Nhiều thuộc tính với Linq


-1

Tôi đến bữa tiệc muộn nhưng không giải pháp nào trong số này hiệu quả với tôi. Tôi rất háo hức dùng thử System.Linq.Dynamic, nhưng tôi không thể tìm thấy điều đó trên Nuget, có thể bị mất giá? Dù bằng cách nào ...

Đây là một giải pháp tôi đã đưa ra. Tôi cần sử dụng động hỗn hợp OrderBy , OrderByDescendingOrderBy> ThenBy .

Tôi chỉ đơn giản là tạo một phương thức mở rộng cho đối tượng danh sách của mình, tôi biết là hơi khó hiểu ... Tôi sẽ không đề xuất điều này nếu đó là điều tôi đã làm rất nhiều, nhưng nó tốt cho bạn.

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);
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.