Динамический LINQ OrderBy на IEnumerable
Я нашел пример в VS2008 примеры для динамического LINQ, который позволяет использовать sql-подобную строку (например,OrderBy("Name, Age DESC")) для заказа. К сожалению, метод включен только работает на IQueryable<T>;. Есть ли способ получить эту функциональность на IEnumerable<T>?
18 ответов:
просто наткнулся на этого старика...
чтобы сделать это без динамической библиотеки 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; }
Edit: он получает больше удовольствия, если вы хотите смешать это с
dynamic- хотя, обратите внимание, чтоdynamicприменяется только к LINQ-to-Objects (деревья выражений для ORMs и т. д. не могут действительно представлять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); } } }
слишком легко без каких-либо осложнений:
- добавить
using System.Linq.Dynamic;в верхней части.- использовать
vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();
Я нашел ответ. Я могу использовать
.AsQueryable<>()метод расширения, чтобы преобразовать мой список в IQueryable, а затем запустить динамический порядок по отношению к нему.
просто наткнулся на этот вопрос.
используя реализацию ApplyOrder Марка сверху, я собрал метод расширения, который обрабатывает 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 несколько предложений orderby а может быть, именно это и искал автор
вот как это сделать:
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использовать customIComparer- потому что я хотел сделать естественная сортировка.чтобы сделать это, я просто изменить
OrderByto: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 }The
вот еще что мне показалось интересным. Если ваш источник является 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); }
альтернативное решение использует следующий класс/интерфейс. Это не совсем динамично, но это работает.
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 in Asp.net и преимущество для сортировки (вы не можете сортировать с помощью IEnumerable версии)
я использовал Dapper как ORM и построил IQueryable версию и использовал сортировку в GridView в asp.net так просто.
Первая Установка Динамическая Инструменты -- > Диспетчер Пакетов NuGet -- > Консоль Диспетчера Пакетов
install-package System.Linq.Dynamicдобавить пространство имен
using System.Linq.Dynamic;Теперь вы можете использовать
OrderBy("Name, Age DESC")
преобразовать список в IEnumerable или Iquerable, добавить с помощью System.Линк.Динамическое пространство имен, то u может упомянуть имена свойств в запятую разделенную строку OrderBy метод, который приходит по умолчанию из системы.Линк.Активный.
var result1 = lst.OrderBy(a=>a.Name);// for ascending order. var result1 = lst.OrderByDescending(a=>a.Name);// for desc order.
Comments