[C#] IEnumerable <T> / IQueryable <T>의 동적 LINQ OrderBy

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



답변

이 노파에 빠졌어

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


답변

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

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

답변

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


답변

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

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

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

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


답변

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

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

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


답변

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

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


답변

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

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

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