Динамический LINQ OrderBy на IEnumerable <T>/ IQueryable <T> - PullRequest
644 голосов
/ 03 сентября 2008

Я нашел пример в VS2008 Примеры для Dynamic LINQ, который позволяет использовать sql-подобную строку (например, OrderBy("Name, Age DESC")) для упорядочения. К сожалению, включенный метод работает только на IQueryable<T>; . Есть ли способ получить эту функцию на IEnumerable<T>?

Ответы [ 19 ]

872 голосов
/ 24 октября 2008

Просто наткнулся на этого старичка ...

Чтобы сделать это без динамической библиотеки 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;
}

Редактировать: становится интереснее, если вы хотите смешать это с dynamic - хотя обратите внимание, что dynamic относится только к LINQ-to-Objects (деревья выражений для ORM и т. Д. Не могут действительно представлять запросы 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);
        }
    }
}
220 голосов
/ 28 декабря 2011

Слишком легко, без каких-либо осложнений:

  1. Добавьте using System.Linq.Dynamic; вверху.
  2. Использование vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();
76 голосов
/ 16 сентября 2008

Я нашел ответ. Я могу использовать метод расширения .AsQueryable<>(), чтобы преобразовать мой список в IQueryable, а затем запустить динамический порядок для него.

52 голосов
/ 18 августа 2010

Просто наткнулся на этот вопрос.

Используя вышеописанную реализацию Marc ApplyOrder, я собрал метод Extension, который обрабатывает SQL-подобные строки, такие как:

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

Подробности можно найти здесь: http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html

41 голосов
/ 03 сентября 2008

Полагаю, было бы неплохо использовать отражение, чтобы получить любое свойство, по которому вы хотите отсортировать:

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

Обратите внимание, что использование отражения значительно медленнее, чем прямой доступ к свойству, поэтому необходимо изучить производительность.

18 голосов
/ 01 апреля 2009

Просто опираясь на то, что сказали другие. Я обнаружил, что следующее работает довольно хорошо.

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;
}
11 голосов
/ 16 декабря 2008

Я наткнулся на этот вопрос в поисках предложений Linq для нескольких заказов. и возможно это было то, что автор искал

Вот как это сделать:

var query = pets.OrderBy(pet => pet.Name).ThenByDescending(pet => pet.Age);    
10 голосов
/ 29 октября 2009

Я пытался сделать это, но у меня возникли проблемы с решением Кжетила Ватнедала , потому что я не использую встроенный синтаксис linq - я предпочитаю синтаксис в стиле метода. Моя конкретная проблема заключалась в попытке выполнить динамическую сортировку с использованием пользовательского 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, - потому что я хотел сделать Естественную сортировку .

Для этого я просто изменяю OrderBy на:

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

См. этот пост для кода NaturalSortComparer().

4 голосов
/ 30 июля 2014

Вы можете конвертировать IEnumerable в IQueryable.

items = items.AsQueryable().OrderBy("Name ASC");
4 голосов
/ 17 апреля 2013

После долгих поисков это сработало для меня:

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