IEnumerable <T> / IQueryable <T>의 동적 LINQ OrderBy


668

VS2008 예제 의 Dynamic LINQ 예제 에서 sql과 같은 문자열을 사용할 수 있는 예를 찾았습니다 (예 : OrderBy("Name, Age DESC"))순서. 불행히도 포함 된 방법은에서만 작동합니다 IQueryable<T>.이 기능을 사용할 수있는 방법이 IEnumerable<T>있습니까?


1
내 의견 으로는 이 날짜 현재 가장 좋은 대답은 System.Linq.Dynamic.Core 라이브러리입니다.
Shahin Dohan

답변:


904

이 노파에 빠졌어

동적 LINQ 라이브러리없이이 작업을 수행하려면 아래 코드가 필요합니다. 여기에는 중첩 속성을 포함한 가장 일반적인 시나리오가 포함됩니다.

그것을 사용하려면 IEnumerable<T>몇 가지 래퍼 메소드를 추가 할 수 있습니다. AsQueryable하지만 아래 코드는 핵심 Expression논리입니다.

public static IOrderedQueryable<T> OrderBy<T>(
    this IQueryable<T> source, 
    string property)
{
    return ApplyOrder<T>(source, property, "OrderBy");
}

public static IOrderedQueryable<T> OrderByDescending<T>(
    this IQueryable<T> source, 
    string property)
{
    return ApplyOrder<T>(source, property, "OrderByDescending");
}

public static IOrderedQueryable<T> ThenBy<T>(
    this IOrderedQueryable<T> source, 
    string property)
{
    return ApplyOrder<T>(source, property, "ThenBy");
}

public static IOrderedQueryable<T> ThenByDescending<T>(
    this IOrderedQueryable<T> source, 
    string property)
{
    return ApplyOrder<T>(source, property, "ThenByDescending");
}

static IOrderedQueryable<T> ApplyOrder<T>(
    IQueryable<T> source, 
    string property, 
    string methodName) 
{
    string[] props = property.Split('.');
    Type type = typeof(T);
    ParameterExpression arg = Expression.Parameter(type, "x");
    Expression expr = arg;
    foreach(string prop in props) {
        // use reflection (not ComponentModel) to mirror LINQ
        PropertyInfo pi = type.GetProperty(prop);
        expr = Expression.Property(expr, pi);
        type = pi.PropertyType;
    }
    Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
    LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);

    object result = typeof(Queryable).GetMethods().Single(
            method => method.Name == methodName
                    && method.IsGenericMethodDefinition
                    && method.GetGenericArguments().Length == 2
                    && method.GetParameters().Length == 2)
            .MakeGenericMethod(typeof(T), type)
            .Invoke(null, new object[] {source, lambda});
    return (IOrderedQueryable<T>)result;
}

편집 : LINQ-to-Object에만 적용 dynamic되지만 dynamicORM 등의 표현식 트리는 실제로 dynamic쿼리를 나타낼 수 MemberExpression는 없지만 지원하지는 않습니다. 그러나 LINQ-to-Objects로 수행하는 방법이 있습니다. 선택은 Hashtable유리한 잠금 의미로 인해 발생합니다.

using Microsoft.CSharp.RuntimeBinder;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Runtime.CompilerServices;
static class Program
{
    private static class AccessorCache
    {
        private static readonly Hashtable accessors = new Hashtable();

        private static readonly Hashtable callSites = new Hashtable();

        private static CallSite<Func<CallSite, object, object>> GetCallSiteLocked(
            string name) 
        {
            var callSite = (CallSite<Func<CallSite, object, object>>)callSites[name];
            if(callSite == null)
            {
                callSites[name] = callSite = CallSite<Func<CallSite, object, object>>
                    .Create(Binder.GetMember(
                                CSharpBinderFlags.None, 
                                name, 
                                typeof(AccessorCache),
                                new CSharpArgumentInfo[] { 
                                    CSharpArgumentInfo.Create(
                                        CSharpArgumentInfoFlags.None, 
                                        null) 
                                }));
            }
            return callSite;
        }

        internal static Func<dynamic,object> GetAccessor(string name)
        {
            Func<dynamic, object> accessor = (Func<dynamic, object>)accessors[name];
            if (accessor == null)
            {
                lock (accessors )
                {
                    accessor = (Func<dynamic, object>)accessors[name];
                    if (accessor == null)
                    {
                        if(name.IndexOf('.') >= 0) {
                            string[] props = name.Split('.');
                            CallSite<Func<CallSite, object, object>>[] arr 
                                = Array.ConvertAll(props, GetCallSiteLocked);
                            accessor = target =>
                            {
                                object val = (object)target;
                                for (int i = 0; i < arr.Length; i++)
                                {
                                    var cs = arr[i];
                                    val = cs.Target(cs, val);
                                }
                                return val;
                            };
                        } else {
                            var callSite = GetCallSiteLocked(name);
                            accessor = target =>
                            {
                                return callSite.Target(callSite, (object)target);
                            };
                        }
                        accessors[name] = accessor;
                    }
                }
            }
            return accessor;
        }
    }

    public static IOrderedEnumerable<dynamic> OrderBy(
        this IEnumerable<dynamic> source, 
        string property)
    {
        return Enumerable.OrderBy<dynamic, object>(
            source, 
            AccessorCache.GetAccessor(property), 
            Comparer<object>.Default);
    }

    public static IOrderedEnumerable<dynamic> OrderByDescending(
        this IEnumerable<dynamic> source, 
        string property)
    {
        return Enumerable.OrderByDescending<dynamic, object>(
            source, 
            AccessorCache.GetAccessor(property), 
            Comparer<object>.Default);
    }

    public static IOrderedEnumerable<dynamic> ThenBy(
        this IOrderedEnumerable<dynamic> source, 
        string property)
    {
        return Enumerable.ThenBy<dynamic, object>(
            source, 
            AccessorCache.GetAccessor(property), 
            Comparer<object>.Default);
    }

    public static IOrderedEnumerable<dynamic> ThenByDescending(
        this IOrderedEnumerable<dynamic> source, 
        string property)
    {
        return Enumerable.ThenByDescending<dynamic, object>(
            source, 
            AccessorCache.GetAccessor(property), 
            Comparer<object>.Default);
    }

    static void Main()
    {
        dynamic a = new ExpandoObject(), 
                b = new ExpandoObject(), 
                c = new ExpandoObject();
        a.X = "abc";
        b.X = "ghi";
        c.X = "def";
        dynamic[] data = new[] { 
            new { Y = a },
            new { Y = b }, 
            new { Y = c } 
        };

        var ordered = data.OrderByDescending("Y.X").ToArray();
        foreach (var obj in ordered)
        {
            Console.WriteLine(obj.Y.X);
        }
    }
}

109
내가 본 가장 빌어 먹을 코드 :) 그냥 내 프로젝트에서 백만 문제를 해결 :)
sajidnizami

4
@ 데이브 - 당신이 시작할 필요가 IQueryable<T>당신 같은이 그렇다면, List<T>(인 IEnumerable<T>) 당신이 사용해야 할 수도 있습니다 AsQueryable()- 예를 들어var sorted = someList.AsQueryable().OrderBy("Foo.Bar");
마크 Gravell

7
이것을 보았습니까? 그것은 일부 사람들에게 도움이 될 수 있습니다 ... stackoverflow.com/questions/557819/… 더 강력한 유형의 솔루션입니다.
anthonyv

28
@ MGOwen 코드의 본질을 오해하는 것 같습니다. 40 줄은 프로젝트 어딘가에 넣은 40 줄이든, 또는 그 줄이 외부 라이브러리에 (사전 컴파일되거나 소스) 오는지에 관계없이 동일합니다. 그것은했을 꽤 놀라운 나는 12 월 '11부터 존재했다 nuget의 라이브러리에 10월 08에 연결 한 경우 (안 적어도 nuget 중 하나를 다음 존재하지 않았기 때문에)에 불과하다 "가 무엇을하고 있는지"기본 똑같다. 또한 모든 코딩 문제에 대해 잘 정의 된 단일 경로가있는 것처럼 "실제 솔루션"이라는 문구를 사용합니다.
Marc Gravell

5
@ MGOwen btw, 외부 lib는 2296 줄의 코드입니다 (AssemblyInfo.cs 제외). 40 개 라인을 꽤 합리적으로 보이게 만드는 것
Marc Gravell

231

합병증없이 너무 쉽습니다 :

  1. using System.Linq.Dynamic;상단에 추가하십시오 .
  2. 사용하다 vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();

11
그리고 어디에서 얻었 System.Linq.Dynamic습니까?
치매

1
linq를 MongoDB와 함께 사용할 때도 작동합니다.
soupy1976

32
수락 된 답변은 2008 년에 정답 일 수 있지만 현재는 가장 쉽고 정확한 답변입니다.
EL MOJO

1
이것은 정말 훌륭하고 간단한
조작법

5
"미래"사용자의 경우 닷넷 코어를 사용하는 경우 다음을 사용하십시오. nuget.org/packages/System.Linq.Dynamic.Core
Rafael Merlin

78

답을 찾았습니다. .AsQueryable<>()확장 방법을 사용하여 내 목록을 IQueryable로 변환 한 다음 이에 대해 동적 순서를 실행할 수 있습니다.


52
나머지 사람들에게 모범을 보여주십시오.
MGOwen

54

이 질문을 우연히 발견했습니다.

위에서 Marc의 ApplyOrder 구현을 사용하여 다음과 같은 SQL과 같은 문자열을 처리하는 Extension 메서드를 함께 사용했습니다.

list.OrderBy("MyProperty DESC, MyOtherProperty ASC");

자세한 내용은 여기에서 찾을 수 있습니다 : http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html


1
대소 문자를 구분하지 않으려면 다음과 같이 수정 사항을 추가하십시오. PropertyInfo pi = type.GetProperty (prop, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
Mrinal Kamboj

43

리플렉션을 사용하여 정렬하려는 속성을 얻는 것이 좋습니다.

IEnumerable<T> myEnumerables
var query=from enumerable in myenumerables
          where some criteria
          orderby GetPropertyValue(enumerable,"SomeProperty")
          select enumerable

private static object GetPropertyValue(object obj, string property)
{
    System.Reflection.PropertyInfo propertyInfo=obj.GetType().GetProperty(property);
    return propertyInfo.GetValue(obj, null);
}

리플렉션 사용은 속성에 직접 액세스하는 것보다 상당히 느리므로 성능을 조사해야합니다.


이것도 작동합니까? ORDERBY는 값 만 선택 람 바어 / 대표 (Func을 <TSource, TKEY> KeySelector에) .. 원하지 않는다
데비 Landman

2
게시하기 전에이 예제를 시도했지만 예, 작동합니다.
Kjetil Watnedal

3
+1 이것은 내가 찾던 것입니다! 이것은 간단한 페이지 정렬 문제에 효과적입니다.
Andrew Siemer

이것은 나를 위해 작동하지 않았습니다. 뭔가 빠졌습니까? "SomeProperty"는 무엇이되어야합니까? property.GetType ()뿐만 아니라 속성 이름을 지정하려고했습니다. IEnumerable <>이 아닌 IQueryable <>이 있습니다.
SO User

2
@Alex Shkor : 모든 요소를 ​​보지 않고 요소를 어떻게 정렬해야합니까? 그러나 다른 답변에는 더 나은 솔루션이 있습니다.
Kjetil Watnedal 님이

19

다른 사람들이 말한 것을 바탕으로합니다. 다음이 잘 작동한다는 것을 알았습니다.

public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> input, string queryString)
{
    if (string.IsNullOrEmpty(queryString))
        return input;

    int i = 0;
    foreach (string propname in queryString.Split(','))
    {
        var subContent = propname.Split('|');
        if (Convert.ToInt32(subContent[1].Trim()) == 0)
        {
            if (i == 0)
                input = input.OrderBy(x => GetPropertyValue(x, subContent[0].Trim()));
            else
                input = ((IOrderedEnumerable<T>)input).ThenBy(x => GetPropertyValue(x, subContent[0].Trim()));
        }
        else
        {
            if (i == 0)
                input = input.OrderByDescending(x => GetPropertyValue(x, subContent[0].Trim()));
            else
                input = ((IOrderedEnumerable<T>)input).ThenByDescending(x => GetPropertyValue(x, subContent[0].Trim()));
        }
        i++;
    }

    return input;
}

12

Linq multiple orderby clauses를 찾고이 질문을 우연히 발견했을 것입니다. 아마도 이것이 저자가 찾고 있었던 것일 수 있습니다.

이를 수행하는 방법은 다음과 같습니다.

var query = pets.OrderBy(pet => pet.Name).ThenByDescending(pet => pet.Age);    

5
+1은 설명 부족으로 다운 투표를 취소했습니다. 또한 저자는 여러 주문에 관심이 있었을 것이라고 생각합니다. 다이내믹 핵심 단어 인 경우에도 투표를 할 이유가 없습니다.
Jason Kleban

11

인라인 linq 구문을 사용하지 않기 때문에이 작업을 시도했지만 Kjetil Watnedal의 솔루션에 문제가 있습니다. 메소드 스타일 구문을 선호합니다. 내 특정 문제는 custom을 사용하여 동적 정렬을 시도하는 것이 었습니다 IComparer.

내 솔루션은 다음과 같이 끝났습니다.

다음과 같은 IQueryable 쿼리가 제공됩니다.

List<DATA__Security__Team> teams = TeamManager.GetTeams();
var query = teams.Where(team => team.ID < 10).AsQueryable();

그리고 런타임 정렬 필드 인수가 주어지면 :

string SortField; // Set at run-time to "Name"

동적 OrderBy는 다음과 같습니다.

query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField));

그리고 GetReflectedPropertyValue ()라는 작은 도우미 메서드를 사용하고 있습니다.

public static string GetReflectedPropertyValue(this object subject, string field)
{
    object reflectedValue = subject.GetType().GetProperty(field).GetValue(subject, null);
    return reflectedValue != null ? reflectedValue.ToString() : "";
}

마지막으로, 나는 자연 정렬 을하고 싶었 기 때문에 OrderBy커스텀을 사용 하고 싶다고 언급했습니다 .IComparer

이를 위해, 나는 OrderByto를 다음과 같이 변경한다 :

query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField), new NaturalSortComparer<string>());

에 대한 코드는 이 게시물 을 참조하십시오 NaturalSortComparer().


5

동적 사용 linq

그냥 추가 using System.Linq.Dynamic;

다음과 같이 사용하여 모든 열을 주문하십시오.

string sortTypeStr = "ASC"; // or DESC
string SortColumnName = "Age"; // Your column name
query = query.OrderBy($"{SortColumnName} {sortTypeStr}");

4

당신은 그것을 추가 할 수 있습니다 :

public static IEnumerable<T> OrderBy( this IEnumerable<T> input, string queryString) {
    //parse the string into property names
    //Use reflection to get and sort by properties
    //something like

    foreach( string propname in queryString.Split(','))
        input.OrderBy( x => GetPropertyValue( x, propname ) );

    // I used Kjetil Watnedal's reflection example
}

GetPropertyValue기능은 Kjetil Watnedal의 답변입니다

문제는 왜 일까? 이러한 정렬은 D2VIANT의 답변과 같이 컴파일 타임이 아닌 런타임에 예외를 throw합니다.

Linq to Sql을 처리하고 orderby가 표현식 트리 인 경우 어쨌든 실행을 위해 SQL로 변환됩니다.


GetPropertyValue mehotod는 모든 요소에 대해 실행되며 나쁜 해결책입니다.
Alex Shkor

2
OrderBy이전 주문을 유지하지 마십시오 !!
아미르 이스마일

4

여기 내가 찾은 흥미로운 것이 있습니다. 소스가 DataTable 인 경우 Dynamic Linq를 사용하지 않고 동적 정렬을 사용할 수 있습니다.

DataTable orders = dataSet.Tables["SalesOrderHeader"];
EnumerableRowCollection<DataRow> query = from order in orders.AsEnumerable()
                                         orderby order.Field<DateTime>("OrderDate")
                                         select order;
DataView view = query.AsDataView();
bindingSource1.DataSource = view;

참조 : http://msdn.microsoft.com/en-us/library/bb669083.aspx(DataSetExtensions 사용)

다음은 DataView로 변환하여 수행하는 또 다른 방법입니다.

DataTable contacts = dataSet.Tables["Contact"];    
DataView view = contacts.AsDataView();    
view.Sort = "LastName desc, FirstName asc";    
bindingSource1.DataSource = view;
dataGridView1.AutoResizeColumns();

4

Maarten 덕분에 ( LINQ에서 PropertyInfo 객체를 사용하여 컬렉션 쿼리 )이 솔루션을 얻었습니다.

myList.OrderByDescending(x => myPropertyInfo.GetValue(x, null)).ToList();

필자의 경우 "ColumnHeaderMouseClick"(WindowsForm)을 작업 중이므로 특정 열과 해당 PropertyInfo를 찾았습니다.

foreach (PropertyInfo column in (new Process()).GetType().GetProperties())
{
    if (column.Name == dgvProcessList.Columns[e.ColumnIndex].Name)
    {}
}

또는

PropertyInfo column = (new Process()).GetType().GetProperties().Where(x => x.Name == dgvProcessList.Columns[e.ColumnIndex].Name).First();

(열 이름이 객체 속성과 일치해야 함)

건배


4

많은 검색 후 이것이 나를 위해 일했습니다.

public static IEnumerable<TEntity> OrderBy<TEntity>(this IEnumerable<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, property.PropertyType },
                                           source.AsQueryable().Expression, 
                                           Expression.Quote(orderByExpression));
    return source.AsQueryable().Provider.CreateQuery<TEntity>(resultExpression);
}

4

IEnumerable을 IQueryable로 변환 할 수 있습니다.

items = items.AsQueryable().OrderBy("Name ASC");

3

대체 솔루션은 다음 클래스 / 인터페이스를 사용합니다. 그것은 역동적이지는 않지만 작동합니다.

public interface IID
{
    int ID
    {
        get; set;
    }
}

public static class Utils
{
    public static int GetID<T>(ObjectQuery<T> items) where T:EntityObject, IID
    {
        if (items.Count() == 0) return 1;
        return items.OrderByDescending(u => u.ID).FirstOrDefault().ID + 1;
    }
}

2

이 답변은 @John Sheehan-Runscope가 제공 한 솔루션에 대한 예제가 필요한 의견에 대한 답변입니다.

나머지 사람들에게 모범을 보여주십시오.

DAL (Data Access Layer)에서

IEnumerable 버전 :

  public  IEnumerable<Order> GetOrders()
    {
      // i use Dapper to return IEnumerable<T> using Query<T>
      //.. do stuff
      return  orders  // IEnumerable<Order>
  }

IQueryable 버전

  public IQueryable<Order> GetOrdersAsQuerable()
    {
        IEnumerable<Order> qry= GetOrders();
        //use the built-in extension method  AsQueryable in  System.Linq namespace
        return qry.AsQueryable();            
    }

이제 IQueryable 버전을 사용하여 바인딩 할 수 있습니다 (예 : Asp.net의 GridView 및 정렬 이점) (IEnumerable 버전을 사용하여 정렬 할 수 없음)

Dapper를 ORM으로 사용하고 IQueryable 버전을 빌드하고 asp.net의 GridView에서 정렬을 쉽게 사용했습니다.


2

먼저 Dynamic Tools-> NuGet Package Manager-> Package Manager Console 설치

install-package System.Linq.Dynamic

네임 스페이스 추가 using System.Linq.Dynamic;

이제 사용할 수 있습니다 OrderBy("Name, Age DESC")


OrderBy ( "Branch.BranchName", "Descending")와 같은 내부 속성 정렬과 함께 사용하는 방법
devC

이것은 나를 위해 작동합니다. 아마도 그 질문은 10 살이 기 때문에이 쉬운 방법은 나중에 나왔습니다.
kosherjellyfish

1

이것을 사용할 수 있습니다 :

        public List<Book> Books(string orderField, bool desc, int skip, int take)
{
    var propertyInfo = typeof(Book).GetProperty(orderField);

    return _context.Books
        .Where(...)
        .OrderBy(p => !desc ? propertyInfo.GetValue(p, null) : 0)
        .ThenByDescending(p => desc ? propertyInfo.GetValue(p, null) : 0)
        .Skip(skip)
        .Take(take)
        .ToList();
}

몇 년 후 나는 이것을 우연히 발견했다. 이것은 꿈처럼 나를 위해 일했습니다. 1-3 속성에 대한 동적 정렬이 있으며 이것은 꿈처럼 작동합니다. 구현이 쉽고 번거 로움이 없습니다.
Bazïnga

0

목록을 IEnumerable 또는 Iquerable로 변환하고 System.LINQ.Dynamic 네임 스페이스를 사용하여 추가 한 다음 기본적으로 System.LINQ.Dynamic에서 제공되는 OrderBy Method에 쉼표로 구분 된 문자열의 속성 이름을 언급 할 수 있습니다.


-3
var result1 = lst.OrderBy(a=>a.Name);// for ascending order. 
 var result1 = lst.OrderByDescending(a=>a.Name);// for desc order. 
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.