Вопрос

Я нашел пример в Примеры VS2008 для динамического 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;
}
<Ч>

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

Другие советы

Слишком просто, без каких-либо осложнений.

<Ол>
  • Добавьте , используя System.Linq.Dynamic; вверху.
  • Используйте vehicle = Vehicles.AsQueryable (). OrderBy (" Make ASC, Year DESC "). ToList ();
  • Я нашел ответ. Я могу использовать метод расширения .AsQueryable < > () , чтобы преобразовать мой список в IQueryable, а затем запустить динамический порядок для него.

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

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

    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 для нескольких заказов. и, возможно, это было то, что искал автор

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

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

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

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

    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 взята из ответа Кгетила Ватнедала

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

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

    Вот еще кое-что, что я нашел интересным. Если ваш источник - 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();
    

    Спасибо Мартену (Запрос коллекции с использованием объекта 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();
    

    (убедитесь, что имена ваших столбцов соответствуют свойствам объекта)

    Ваше здоровье

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

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

    Вы можете преобразовать IEnumerable в IQueryable.

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

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

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

    Этот ответ является ответом на комментарии, которым нужен пример решения, предоставленного @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.

    Первая установка Dynamic Инструменты - > Менеджер пакетов NuGet - > Консоль диспетчера пакетов

    install-package System.Linq.Dynamic
    

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

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

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

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

    просто добавьте с помощью System.Linq.Dynamic;

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

    string sortTypeStr = "ASC"; // or DESC
    string SortColumnName = "Age"; // Your column name
    query = query.OrderBy(<*>quot;{SortColumnName} {sortTypeStr}");
    
    var result1 = lst.OrderBy(a=>a.Name);// for ascending order. 
     var result1 = lst.OrderByDescending(a=>a.Name);// for desc order. 
    
    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top