Динамический LINQ OrderBy на IEnumerable

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

Просто наткнулся на это старое …

Для этого без динамической библиотеки LINQ вам просто нужен код, как показано ниже. Это охватывает наиболее распространенные сценарии, включая вложенные свойства.

Чтобы заставить его работать с IEnumerable вы можете добавить некоторые методы-обертки, которые идут через AsQueryable но приведенный ниже код является основной логикой Expression .

 public static IOrderedQueryable OrderBy( this IQueryable source, string property) { return ApplyOrder(source, property, "OrderBy"); } public static IOrderedQueryable OrderByDescending( this IQueryable source, string property) { return ApplyOrder(source, property, "OrderByDescending"); } public static IOrderedQueryable ThenBy( this IOrderedQueryable source, string property) { return ApplyOrder(source, property, "ThenBy"); } public static IOrderedQueryable ThenByDescending( this IOrderedQueryable source, string property) { return ApplyOrder(source, property, "ThenByDescending"); } static IOrderedQueryable ApplyOrder( IQueryable 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)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> GetCallSiteLocked( string name) { var callSite = (CallSite>)callSites[name]; if(callSite == null) { callSites[name] = callSite = CallSite> .Create(Binder.GetMember( CSharpBinderFlags.None, name, typeof(AccessorCache), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create( CSharpArgumentInfoFlags.None, null) })); } return callSite; } internal static Func GetAccessor(string name) { Func accessor = (Func)accessors[name]; if (accessor == null) { lock (accessors ) { accessor = (Func)accessors[name]; if (accessor == null) { if(name.IndexOf('.') >= 0) { string[] props = name.Split('.'); CallSite>[] 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 OrderBy( this IEnumerable source, string property) { return Enumerable.OrderBy( source, AccessorCache.GetAccessor(property), Comparer.Default); } public static IOrderedEnumerable OrderByDescending( this IEnumerable source, string property) { return Enumerable.OrderByDescending( source, AccessorCache.GetAccessor(property), Comparer.Default); } public static IOrderedEnumerable ThenBy( this IOrderedEnumerable source, string property) { return Enumerable.ThenBy( source, AccessorCache.GetAccessor(property), Comparer.Default); } public static IOrderedEnumerable ThenByDescending( this IOrderedEnumerable source, string property) { return Enumerable.ThenByDescending( source, AccessorCache.GetAccessor(property), Comparer.Default); } static void Main() { dynamic a = new ExpandoObject(), b = new ExpandoObject(), c = new ExpandoObject(); aX = "abc"; bX = "ghi"; cX = "def"; dynamic[] data = new[] { new { Y = a }, new { Y = b }, new { Y = c } }; var ordered = data.OrderByDescending("YX").ToArray(); foreach (var obj in ordered) { Console.WriteLine(obj.YX); } } } 

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

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

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

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

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

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

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

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

 IEnumerable 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 OrderBy(this IEnumerable 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)input).ThenBy(x => GetPropertyValue(x, subContent[0].Trim())); } else { if (i == 0) input = input.OrderByDescending(x => GetPropertyValue(x, subContent[0].Trim())); else input = ((IOrderedEnumerable)input).ThenByDescending(x => GetPropertyValue(x, subContent[0].Trim())); } i++; } return input; } 

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

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

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

Я пытался это сделать, но имел проблемы с решением Kjetil Watnedal, потому что я не использую синтаксис inline linq – я предпочитаю синтаксис стиля метода. Моя особая проблема заключалась в попытке выполнить динамическую сортировку с помощью пользовательского IComparer .

Мое решение получилось так:

Учитывая такой запрос IQueryable:

 List 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()); 

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

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

 public static IEnumerable OrderBy( this IEnumerable 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 относится к ответу Kjetil Watnedal

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

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

Вот что еще я нашел интересным. Если ваш источник является DataTable, вы можете использовать динамическую сортировку без использования Dynamic Linq

 DataTable orders = dataSet.Tables["SalesOrderHeader"]; EnumerableRowCollection query = from order in orders.AsEnumerable() orderby order.Field("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(); 

Благодаря 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(); 

(обязательно, чтобы имена столбцов соответствовали объекту Properties)

ура

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

 public static IEnumerable OrderBy(this IEnumerable 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(resultExpression); } 

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

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

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

 public interface IID { int ID { get; set; } } public static class Utils { public static int GetID(ObjectQuery 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 GetOrders() { // i use Dapper to return IEnumerable using Query //.. do stuff return orders // IEnumerable } 

Версия IQueryable

  public IQueryable GetOrdersAsQuerable() { IEnumerable 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.

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

Первая установка динамических инструментов -> Диспетчер пакетов NuGet -> Консоль диспетчера пакетов

 install-package System.Linq.Dynamic 

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

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

 var result1 = lst.OrderBy(a=>a.Name);// for ascending order. var result1 = lst.OrderByDescending(a=>a.Name);// for desc order. 
Давайте будем гением компьютера.