Я нашел пример в примерах VS2008 для динамического LINQ, который позволяет использовать sql-подобная строка (например, OrderBy("Name, Age DESC"))
для упорядочивания. К сожалению, включенный метод работает только на IQueryable<T>
. Есть ли способ получить эту функциональность на IEnumerable<T>
?
Динамический порядок LINQ от IEnumerable ‹T› / IQueryable ‹T›
Ответы (19)
Только что наткнулся на эту старичку ...
Чтобы сделать это без динамической библиотеки 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);
}
}
}
LIKE
немного другое, но какой сервер? Для LINQ-to-SQL существует SqlMethods.Like
: msdn.microsoft.com/en-us/library/; Вы можете добавить больше информации о том, что ищете?
- person Marc Gravell; 02.11.2009
SqlMethods.Like
в пользовательском выражении; действительно непонятно где ломается ... можете уточнить? Может задать вопрос?
- person Marc Gravell; 02.11.2009
IQueryable<T>
, поэтому, если у вас есть что-то вроде List<T>
(то есть IEnumerable<T>
), вам может потребоваться использовать AsQueryable()
- например var sorted = someList.AsQueryable().OrderBy("Foo.Bar");
- person Marc Gravell; 22.01.2010
var ordered = someData.OrderBy("Name");
- или для IEnumerable<T>
данных var ordered = someData.AsQueryable().OrderBy("Name");
- person Marc Gravell; 03.06.2010
.OrderBy(x => x.Something)
(умножить на 10 или что-то еще) - скорее всего, switch
- person Marc Gravell; 29.04.2011
IEnumerable
, просто заменив lambda
на lambda.Compile()
в предпоследней строке, исключив очевидные замены IQueryable
›IEnumerable
и IOrderedQueryable
на IOrderedEnumerable
. Обертки не задействованы
- person Piddu; 22.05.2013
IQueryable<T>
, он включается в запрос без выполнения каких-либо действий в памяти.
- person Marc Gravell; 13.01.2014
System.Linq.Dynamic
.
- person Shawn Beachy; 27.07.2018
#nullable disable
- person Marc Gravell; 14.10.2019
Слишком просто без каких-либо осложнений:
- Добавьте
using System.Linq.Dynamic;
вверху. - Используйте
vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();
Изменить: для экономии времени сборка System.Linq.Dynamic.Core (System.Linq.Dynamic устарела) не является частью платформы, но ее можно установить. из nuget: System.Linq.Dynamic.Core
System.Linq.Dynamic
?
- person Rafael Herscovici; 25.02.2013
vehicles.AsQueryable().OrderBy("Tire.Size").ToList();
, где Tire
- это объект, принадлежащий Vehicle
.
- person neilsimp1; 11.01.2019
Просто наткнулся на этот вопрос.
Используя реализацию Марка 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 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
использовал пользовательский IComparer
, потому что я хотел сделать Естественная сортировка.
Для этого я просто изменяю OrderBy
на:
query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField), new NaturalSortComparer<string>());
Код см. В этом сообщении для NaturalSortComparer()
.
Использовать динамический linq
просто добавьте using System.Linq.Dynamic;
И используйте его так, чтобы упорядочить все свои столбцы:
string sortTypeStr = "ASC"; // or DESC
string SortColumnName = "Age"; // Your column name
query = query.OrderBy($"{SortColumnName} {sortTypeStr}");
После долгих поисков это сработало для меня:
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 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 для выполнения.
OrderBy
не сохраняйте прежний порядок !!
- person Amir Ismail; 09.04.2012
Вот еще кое-что, что я нашел интересным. Если вашим источником является 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)
{}
}
OR
PropertyInfo column = (new Process()).GetType().GetProperties().Where(x => x.Name == dgvProcessList.Columns[e.ColumnIndex].Name).First();
(убедитесь, что имена ваших столбцов соответствуют свойствам объекта)
Ваше здоровье
Вы можете преобразовать IEnumerable в IQueryable.
items = items.AsQueryable().OrderBy("Name ASC");
Сначала установите динамические Инструменты -> Диспетчер пакетов NuGet -> Консоль диспетчера пакетов
install-package System.Linq.Dynamic
Добавить пространство имен using System.Linq.Dynamic;
Теперь вы можете использовать OrderBy("Name, Age DESC")
Вы можете использовать это:
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();
}
Альтернативное решение использует следующий класс / интерфейс. Это не совсем динамично, но работает.
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.
Преобразуйте List в IEnumerable или Iquerable, добавьте, используя пространство имен System.LINQ.Dynamic, затем вы можете указать имена свойств в строке, разделенной запятыми, в метод OrderBy, который по умолчанию поступает из System.LINQ.Dynamic.
вы можете сделать это для нескольких заказов
IOrderedEnumerable<JToken> sort;
if (query.OrderBys[0].IsDESC)
{
sort = jarry.OrderByDescending(r => (string)r[query.OrderBys[0].Key]);
}
else
{
sort = jarry.OrderBy(r =>
(string) r[query.OrderBys[0].Key]);
}
foreach (var item in query.OrderBys.Skip(1))
{
if (item.IsDESC)
{
sort = sort.ThenByDescending(r => (string)r[item.Key]);
}
else
{
sort = sort.ThenBy(r => (string)r[item.Key]);
}
}