Динамический LINQ OrderBy для IEnumerable / IQueryable

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

3.09.2008 06:30:31
На мой взгляд, лучший ответ на эту дату: библиотека System.Linq.Dynamic.Core .
Shahin Dohan 7.10.2018 22:50:37
20 ОТВЕТОВ
РЕШЕНИЕ

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

Чтобы сделать это без динамической библиотеки 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);
        }
    }
}
901
11.12.2017 08:52:02
Лучшая чертова часть кода, которую я видел :) Только что решил миллион проблем в моем проекте :)
sajidnizami 20.11.2008 09:37:25
@ Dave - вы должны начать с IQueryable<T>, так что если у вас есть что - то подобное List<T>(что IEnumerable<T>) вам может понадобиться использование AsQueryable()- напримерvar sorted = someList.AsQueryable().OrderBy("Foo.Bar");
Marc Gravell♦ 22.01.2010 19:26:04
Вы видели это ... это могло бы помочь некоторым людям ... stackoverflow.com/questions/557819/… это более строго типизированное решение.
anthonyv 8.05.2010 12:30:15
@MGO, кажется, вы неправильно понимаете природу кода. 40 строк одинаковы, независимо от того, что это 40 строк, которые вы положили где-то в своем проекте, или эти строки (предварительно скомпилированные или как исходные) во внешней библиотеке. Было бы довольно удивительно, если бы я связал в октябре '08 с библиотекой nuget, которая существует с декабря '11 (не в последнюю очередь потому, что тогда и не существовало nuget), но фундаментальное «что он делает» это то же. Кроме того, вы используете фразу «фактическое решение», как будто есть какой-то четко определенный согласованный единый путь к каждому вопросу кодирования: нет.
Marc Gravell♦ 13.07.2013 08:15:07
@MGOwen, кстати, внешняя библиотека содержит 2296 строк кода (не включая AssemblyInfo.cs); из-за чего эти 40 строк выглядят вполне разумно
Marc Gravell♦ 13.07.2013 08:18:13

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

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

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

43
28.10.2008 07:21:49
это вообще работает? orderby не хочет значения, но селектор lamba / делегат (Func <TSource, TKey> keySelector) ..
Davy Landman 24.10.2008 12:58:45
Я попробовал этот пример перед публикацией, и да, он работает.
Kjetil Watnedal 28.10.2008 07:22:16
+1 Это именно то, что я искал! Это отлично подойдет для простых задач сортировки страниц.
Andrew Siemer 19.04.2010 04:08:27
Это не сработало для меня. Я что-то упускаю? Каким должен быть "SomeProperty". Я попытался дать имя свойства, а также property.GetType (). У меня есть IQueryable <>, а не IEnumerable <>
SO User 23.07.2010 05:59:53
@ Алекс Шкор: Как вы должны сортировать элементы, не глядя на все элементы? Однако в других ответах есть лучшие решения.
Kjetil Watnedal 15.02.2012 12:58:22

Вы можете добавить это:

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Функция от ответа Кьетил Watnedal в

Вопрос был бы почему? Любая такая сортировка будет генерировать исключения во время выполнения, а не во время компиляции (как ответ D2VIANT).

Если вы имеете дело с Linq to Sql, а orderby - это дерево выражений, оно все равно будет преобразовано в SQL для выполнения.

5
23.05.2017 12:34:37
Mehotod GetPropertyValue будет выполнен для всех элементов, это плохое решение.
Alex Shkor 14.02.2012 16:27:29
OrderByне поддерживать предыдущий порядок!
Amir Ismail 9.04.2012 09:13:37

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

78
30.08.2011 02:25:23
Пожалуйста, приведите пример для остальных из нас.
MGOwen 12.07.2013 03:05:29

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

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

var query = pets.OrderBy(pet => pet.Name).ThenByDescending(pet => pet.Age);    
12
27.04.2015 21:48:37
+1 отменил отрицательное голосование из-за отсутствия объяснения. Я также думаю, что автор мог быть заинтересован в нескольких заказах. Даже если динамическое было ключевым словом, нет причин для отрицательного голосования.
Jason Kleban 25.05.2010 18:51:04

Альтернативное решение использует следующий класс / интерфейс. Это не совсем динамично, но работает.

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;
    }
}
3
16.03.2009 02:05:29

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

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;
}
19
16.12.2015 09:36:22

Я пытался сделать это, но у меня возникли проблемы с решением Kjetil Watnedal, потому что я не использую встроенный синтаксис 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().

11
21.10.2018 03:28:46

Вот еще кое-что, что я нашел интересным. Если ваш источник - 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
13.01.2010 20:01:38

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

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

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

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

54
18.08.2010 01:55:30
Отличная вещь, просто добавьте модификацию следующим образом, чтобы сделать имя свойства нечувствительным к регистру: PropertyInfo pi = type.GetProperty (prop, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
Mrinal Kamboj 29.05.2016 07:47:13

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

  1. Добавьте using System.Linq.Dynamic;вверху.
  2. использование vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();
230
16.12.2015 09:35:50
а откуда ты взял System.Linq.Dynamic?
Dementic 25.02.2013 14:12:29
Работает и при использовании linq с MongoDB.
soupy1976 24.07.2013 10:02:34
Возможно, принятый ответ был правильным в 2008 году, но в настоящее время это самый простой, самый правильный ответ на данный момент.
EL MOJO 24.10.2014 15:33:04
Это действительно хорошая и простая обработка, такая внутренняя сложность, мне очень понравилось
Mrinal Kamboj 28.05.2016 14:25:59
Для людей в «будущем», если вы используете ядро ​​dotnet, используйте это: nuget.org/packages/System.Linq.Dynamic.Core
Rafael Merlin 1.11.2017 02:08:36

Благодаря Maarten ( Запрос коллекции с использованием объекта PropertyInfo в LINQ ) я получил это решение:

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
23.05.2017 11:47:27

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

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
24.10.2013 13:29:15

Преобразуйте List в IEnumerable или Iquerable, добавьте с помощью пространства имен System.LINQ.Dynamic, после чего вы можете упомянуть имена свойств в строке, разделенной запятой, в метод OrderBy, который по умолчанию поставляется из System.LINQ.Dynamic.

0
5.08.2013 15:37:27

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

items = items.AsQueryable().OrderBy("Name ASC");
4
30.07.2014 10:29:40
var result1 = lst.OrderBy(a=>a.Name);// for ascending order. 
 var result1 = lst.OrderByDescending(a=>a.Name);// for desc order. 
-3
16.05.2016 10:01:34

Этот ответ является ответом на комментарии, которым нужен пример решения, предоставленного @John Sheehan - Runscope

Пожалуйста, приведите пример для остальных из нас.

в DAL (уровень доступа к данным),

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 для привязки, например, GridView в Asp.net и воспользоваться преимуществами для сортировки (вы не можете сортировать, используя версию IEnumerable)

Я использовал Dapper в качестве ORM и собрал версию IQueryable и использовал сортировку в GridView в asp.net так легко.

2
23.11.2017 20:34:58

Сначала установите Dynamic Tools -> Диспетчер пакетов NuGet -> Консоль диспетчера пакетов

install-package System.Linq.Dynamic

Добавить пространство имен using System.Linq.Dynamic;

Теперь вы можете использовать OrderBy("Name, Age DESC")

2
10.03.2018 18:31:00
Как я могу использовать это с внутренней сортировкой свойств - как OrderBy ("Branch.BranchName", "Descending")
devC 23.05.2019 11:08:53
Это работает для меня. Возможно, потому что вопрос 10 лет, и этот более простой метод пришел позже.
kosherjellyfish 12.07.2019 06:49:54

Используйте динамический linq

просто добавь using System.Linq.Dynamic;

И используйте это так, чтобы упорядочить все ваши столбцы:

string sortTypeStr = "ASC"; // or DESC
string SortColumnName = "Age"; // Your column name
query = query.OrderBy($"{SortColumnName} {sortTypeStr}");
4
25.12.2018 10:24:38

Вы можете использовать это:

        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
16.03.2020 18:36:35
Через пару лет я наткнулся на это; это сработало для меня, как во сне. У меня есть динамическая сортировка от 1 до 3 свойств, и это работает как сон. Легко реализовать и без проблем.
Bazïnga 22.03.2020 06:44:32