Linq OrderBy 인수를 어떻게 동적으로 지정합니까?


94

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를
찾으실

@Nev_Rahd : 질문을 조금 명확히하려고했습니다. 또한, OrderBy의 LINQ 기능, 그리고 켜져 IEnumerable에 기능의 특정 없습니다 List. :) 편집 다시 롤백 또는 추가 변경 자유롭게
Merlyn 모건 - 그레이엄에게

답변:


129

여기에 반사를 사용하는 가능성이 있습니다 ...

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

3
하지만 Entity Framework (SQL Server 또는 기타)와 같은 공급자가 해석하는 Linq 식에 관해서는 사실입니까?
a.boussema 2013

2
@vijay- ThenBy방법을 사용합니다 .
codeConcussion

7
시도 할 때 오류가 발생합니다. 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
내가 어떻게 동적으로 오름차순 또는 내림차순으로 순서를 결정할 수 있습니다
Hitesh Modha

123

약간의 리플렉션을 사용하여 다음과 같이 표현식 트리를 구성 할 수 있습니다 (확장 방법입니다).

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정렬 할 속성 이름이며에 대한 매개 변수로 true를 전달하면 desc내림차순으로 정렬됩니다. 그렇지 않으면 오름차순으로 정렬됩니다.

이제 할 수 existingStudents.OrderBy("City",true);있거나existingStudents.OrderBy("City",false);


10
이 대답은 굉장하고 반성 대답보다 훨씬 낫습니다. 이것은 실제로 엔티티 프레임 워크와 같은 다른 공급자와 함께 작동합니다.
Sam

2
내가 할 수만 있다면 이것을 10 번 찬성 할 것이다 !!! 이와 같은 확장 메소드 작성 방법은 어디에서 배우나요 ?? !!
Jach

3
내장 된 OrderBy처럼 IOrderedQueryable을 반환해야합니까? 그렇게하면 .ThenBy를 호출 할 수 있습니다.
Patrick Szalapski

4
EFCore 3.0을 사용할 때 더 이상 작동하지 않는 것 같습니다. 쿼리를 번역 할 수없는 런타임 오류가 발생합니다.
Mildan 19.10.04

3
예, @Mildan, 이것은 저에게도 3.0과 3.1에서 중단됩니다. ~ "cant translate"오류가 있습니다. 관련이있는 경우 MySQl에 Pomelo를 사용합니다. 문제는 표현입니다. 표현을 직접 코딩하면 작동합니다. 따라서 Lambda.Expression () 대신 다음과 같은 것을 제공하십시오. LambdaExpression orderByExp1 = (Expression <Func <AgencySystemBiz, string >>) (x => x.Name);
Menace

10

@Icarus답변 을 확장하려면 확장 메서드의 반환 유형이 IQueryable 대신 IOrderedQueryable이되도록하려면 다음과 같이 결과를 간단히 캐스팅 할 수 있습니다.

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에 적합하지 않은 것 같습니다. Linq to Entities는 GetProperty, GetValue를 지원하지 않으므로 EF를위한 완벽한 솔루션입니다.
Bill

1
이 방법은 3.0과 3.1에서 실패한 것 같습니다 (2.2에서 작동했습니다). 관련성이있을 수 있도록 MySql에 Pomelo를 사용합니다. 해결 방법이 있지만 추악합니다. 위의 내 의견을 참조하십시오.
Menace

이것은 EF 3.0에서 저에게 효과적이었습니다. 그러나 프런트 엔드가 대 / 소문자 구분을 일치시킬 필요가 없도록 다음 줄을 변경해야합니다. var property = type.GetProperty (OrderByProperty, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
아서 왕 3 세

여전히 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) 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);
    }

설명

System.Linq.Dynamic을 사용하면 OrderBy 메서드에서 문자열 값을 설정할 수 있습니다. 그러나이 확장 내에서 문자열은 Lambda로 구문 분석됩니다. 그래서 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

조건부 내림차순을 처리하기 위해 제가 생각해 낸 것이 있습니다. 이를 keySelector동적으로 func 를 생성하는 다른 방법과 결합 할 수 있습니다.

    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을 취하는 다른 오버로드 도 있습니다 . 당신은 또한 그것들을 조사하고 그들이 당신에게 유용한 것을 제공하는지 볼 수 있습니다.Expression<Func<TSource, TKey>>IComparer<TKey>


TKey 유형을 정의하지 않았으므로 작동하지 않습니다. 대신 <T>를 사용하려면 <T>를 변경해야합니다.
Patrick Desjardins 2014

이것은 나를 위해 일한 것입니다! 전달 된 bool 값에 따라 목록을 오름차순 또는 내림차순으로 정렬하는 함수를 원했습니다. 당신의 코드는 약간의 수정으로 훌륭하게 작동했습니다!
Joe Gayetty

LINQ in Action : IEnumerable <Book> CustomSort <TKey> (Func <Book, TKey> selector, Boolean ascending) {IEnumerable <Book> books = SampleData.Books; 오름차순 반환? books.OrderBy (selector) : books.OrderByDescending (selector); }
Leszek P

1

나를 위해 일한 유일한 솔루션은 neoGeneva가 https://gist.github.com/neoGeneva/1878868 에 게시했습니다 .

나는 그의 코드가 잘 작동하고 인터 웹에서 손실되는 것을 원하지 않기 때문에 다시 게시 할 것입니다!

    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 쿼리와 같이 쿼리별로 순서를 지정합니다. 예 : students.OrderBy ( "City DESC, Address"). 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);
        }

이것은 람다 표현식에 대해 본문이 반환하는 함수이며 string 및 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로 다중 속성 람다 식을 어떻게 만들 수 있습니까?


-1

나는 파티에 늦었지만 이러한 솔루션 중 어느 것도 나를 위해 일하지 않았습니다. System.Linq.Dynamic을 사용하고 싶었지만 Nuget에서 찾을 수 없었거나 감가 상각되었을 수 있습니까? 어느 쪽이든 ...

여기 내가 생각해 낸 해결책이 있습니다. OrderBy , OrderByDescendingOrderBy> 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.