Динамічний LINQ OrderBy на IEbrobro <T> / IQueryable <T>


668

Я знайшов приклад у прикладах VS2008 для Dynamic LINQ, який дозволяє використовувати рядок, схожий на sql (наприклад, OrderBy("Name, Age DESC"))для замовлення. На жаль, включений метод працює лише IQueryable<T>. Чи є спосіб увімкнути цю функціональність IEnumerable<T>?


1
Найкраща відповідь на цю дату, на мій погляд: Бібліотека System.Linq.Dynamic.Core .
Шахін Дохан

Відповіді:


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

Редагувати: це стає веселіше, якщо ви хочете змішати це з dynamic- хоча зауважте, що це dynamicстосується лише LINQ-до-об’єктів (дерева виразів для 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);
        }
    }
}

109
Найкращий проклятий фрагмент коду, який я бачив :) Щойно вирішив мільйон проблем у своєму проекті :)
sajidnizami

4
@Dave - вам потрібно почати IQueryable<T>, тому якщо у вас є щось на зразок List<T>(що є IEnumerable<T>), можливо, вам доведеться скористатися AsQueryable()- наприклад,var sorted = someList.AsQueryable().OrderBy("Foo.Bar");
Марк Гравелл

7
Ви бачили це ... це може допомогти деяким людям ... stackoverflow.com/questions/557819/… його більш сильно набране рішення.
антонів

28
@MGOwen ви, схоже, неправильно розумієте природу коду. 40 рядків - це те саме, незалежно від того, чи є це 40 рядків, які ви розміщуєте десь у своєму проекті, або якщо ці рядки надходять (попередньо складені або як джерело) у зовнішній бібліотеці. Було б досить дивовижно, якби я пов’язав, у жовтні08 року, з бібліотекою, яка існувала з грудня 11 року (не в останню чергу тому, що нугета тоді ще не існувало), але основне "те, що вона робить", - це так само. Крім того, ви використовуєте словосполучення "фактичне рішення" так, ніби є якийсь чітко визначений узгоджений єдиний маршрут до кожного питання кодування: немає.
Марк Гравелл

5
@MGOwen btw, зовнішня ліб - 2296 рядків коду (не включаючи AssemblyInfo.cs); що
нібито

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
Це дійсно добре і просте управління, стільки складних внутрішньо, це сподобалось
Mrinal Kamboj

5
Для людей у ​​майбутньому, якщо ви використовуєте ядро dotnet
Рафаель Мерлін

78

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


52
Надайте приклад для решти нас.
MGOwen

54

Просто натрапив на це питання.

Використовуючи реалізацію Marc's ApplicationOrder зверху, я з'єднав метод розширення, який обробляє рядки, подібні SQL, як:

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 не бажає значення, але вибранець lamba / delegate (Func <TSource, TKey> keySelector) ..
Деві

2
Я намагався цей приклад перед публікацією, і так, він спрацьовує.
Kjetil Watnedal

3
+1 Це саме те, що я шукав! Це чудово підійде для простих питань сортування сторінок.
Ендрю Сімер

Це не спрацювало для мене. Я щось пропускаю? Що має бути "SomeProperty". Я спробував вказати ім’я власності, а також property.GetType (). У мене є IQueryable <>, а не IEnumerable <>
SO Користувач

2
@ Алекс Шкор: Як ти повинен сортувати елементи, не дивлячись на всі елементи? Однак в інших відповідях є кращі рішення.
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 декількох замовних статей, і, можливо, саме це шукав автор

Ось як це зробити:

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

5
+1 скасував перелік голосів через відсутність пояснень. Я також думаю, що автора могли зацікавити кілька замовлень. Навіть якщо ключове слово було динамічним , то немає причин для голосування.
Джейсон Клебан

11

Я намагався це зробити, але виникли проблеми з рішенням 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().


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
}

The GetPropertyValueФункція від відповіді Кьєтіл Watnedal в

Питання було б чому? Будь-який подібний тип закидає винятки під час виконання, а не час компіляції (як відповідь D2VIANT).

Якщо ви маєте справу з Linq для Sql, а orderby - це дерево вираження, воно все одно буде перетворено в SQL для виконання.


GetPropertyValue mehotod буде виконаний для всіх елементів, це погане рішення.
Алекс Шкор

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 ( Запитайте колекцію за допомогою об’єкта 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

Після багатьох пошуків це працювало для мене:

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


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 (рівень доступу до даних),

Версія 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

Спочатку встановіть динамічні інструменти -> Менеджер пакунків NuGet -> Консоль диспетчера пакунків

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 простір імен, тоді u можете згадати імена властивостей у відокремленій комою рядку до методу OrderBy, який за замовчуванням надходить із System.LINQ.Dynamic.


-3
var result1 = lst.OrderBy(a=>a.Name);// for ascending order. 
 var result1 = lst.OrderByDescending(a=>a.Name);// for desc order. 
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.