Dynamic LINQ OrderBy on IEnumerable
Znalazłem przykład w VS2008 Examples dla dynamicznego LINQ, który pozwala na użycie ciągu podobnego do sql (np. OrderBy("Name, Age DESC"))
do zamawiania. Niestety, dołączona metoda działa tylko na IQueryable<T>
;. Czy jest jakiś sposób na włączenie tej funkcjonalności IEnumerable<T>
?
18 answers
Natknąłem się na tego starego...
Aby to zrobić bez dynamicznej biblioteki LINQ, wystarczy Kod jak poniżej. Obejmuje to najczęściej występujące scenariusze, w tym zagnieżdżone właściwości.
Aby go uruchomić z IEnumerable<T>
możesz dodać kilka metod wrappera, które przechodzą przez AsQueryable
- ale poniższy kod jest podstawową logiką 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: będzie zabawniej, jeśli chcesz mieszać to z {[5] } - chociaż zauważ, że dynamic
dotyczy tylko LINQ-to-Objects (expression-trees for orms etc can 't really represent dynamic
queries - MemberExpression
doesn' t support it). Ale oto sposób, aby to zrobić z LINQ-to-Objects. Należy zauważyć, że wybór Hashtable
wynika z korzystnej semantyki blokowania:
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);
}
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-11 08:52:02
Zbyt łatwe bez żadnych komplikacji:
- Dodaj
using System.Linq.Dynamic;
Na górze. - Użyj
vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-16 09:35:50
Znalazłem odpowiedź. Mogę użyć metody rozszerzenia .AsQueryable<>()
, Aby przekonwertować moją listę do IQueryable, a następnie uruchomić kolejność dynamiczną przez nią.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-30 02:25:23
Natknąłem się na to pytanie.
Używając implementacji ApplyOrder Marc z góry, połączyłem metodę rozszerzenia obsługującą ciągi podobne do SQL:
list.OrderBy("MyProperty DESC, MyOtherProperty ASC");
Szczegóły można znaleźć tutaj: http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-18 01:55:30
Myślę, że byłoby dobrze użyć refleksji, aby uzyskać jakąkolwiek nieruchomość chcesz posortować:
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);
}
Zauważ, że używanie reflection jest znacznie wolniejsze niż bezpośrednie uzyskiwanie dostępu do właściwości, więc wydajność musi być zbadana.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-28 07:21:49
Bazując na tym, co powiedzieli inni. Okazało się, że następujące działa całkiem dobrze.
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;
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-16 09:36:22
Natknąłem się na to pytanie szukając klauzul Linq multiple orderby a może właśnie tego szukał autor
Oto Jak to zrobić:
var query = pets.OrderBy(pet => pet.Name).ThenByDescending(pet => pet.Age);
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-27 21:48:37
Próbowałem to zrobić, ale mam problemy z rozwiązaniem Kjetila Watnedala ponieważ nie używam składni inline linq-wolę składnię w stylu metody. Mój szczególny problem polegał na próbie dynamicznego sortowania przy użyciu niestandardowego IComparer
.
Moje rozwiązanie skończyło się tak:
Podane zapytanie IQueryable jak tak:
List<DATA__Security__Team> teams = TeamManager.GetTeams();
var query = teams.Where(team => team.ID < 10).AsQueryable();
I podano argument pola sortowania w czasie wykonywania:
string SortField; // Set at run-time to "Name"
Kolejność dynamiczna wygląda tak:
query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField));
I to za pomocą mała metoda pomocnicza o nazwie GetReflectedPropertyValue ():
public static string GetReflectedPropertyValue(this object subject, string field)
{
object reflectedValue = subject.GetType().GetProperty(field).GetValue(subject, null);
return reflectedValue != null ? reflectedValue.ToString() : "";
}
Jeszcze jedno-wspomniałem, że chciałem, aby OrderBy
używało custom IComparer
- ponieważ chciałem wykonaćnaturalne sortowanie .
Aby to zrobić, zmieniam OrderBy
na:
query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField), new NaturalSortComparer<string>());
Zobacz ten post Po kod dla NaturalSortComparer()
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:27
Możesz dodać:
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
}
Funkcja GetPropertyValue
pochodzi z odpowiedzi Kjetila Watnedala
Problemem byłoby dlaczego? Każdy taki rodzaj wyrzuca wyjątki w czasie wykonywania, a nie kompilacji (jak odpowiedź D2VIANTA).
Jeśli masz do czynienia z Linq do Sql, a orderby jest drzewem wyrażeń, to i tak zostanie przekonwertowany na SQL do wykonania.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:37
Jest jeszcze coś, co mnie zainteresowało. Jeśli twoje źródło jest DataTable, możesz użyć sortowania dynamicznego bez użycia dynamicznego 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;
Odniesienie: http://msdn.microsoft.com/en-us/library/bb669083.aspx (Using DataSetExtensions)
Jest jeszcze jeden sposób, aby to zrobić, konwertując go do DataView:
DataTable contacts = dataSet.Tables["Contact"];
DataView view = contacts.AsDataView();
view.Sort = "LastName desc, FirstName asc";
bindingSource1.DataSource = view;
dataGridView1.AutoResizeColumns();
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-13 20:01:38
Dzięki Maarten ( zapytanie kolekcji za pomocą obiektu PropertyInfo w LINQ ) otrzymałem takie rozwiązanie:
myList.OrderByDescending(x => myPropertyInfo.GetValue(x, null)).ToList();
W moim przypadku pracowałem nad "ColumnHeaderMouseClick" (WindowsForm), więc po prostu znalazłem konkretną kolumnę wciśniętą i jej odpowiedni Właściwośćinfo:
foreach (PropertyInfo column in (new Process()).GetType().GetProperties())
{
if (column.Name == dgvProcessList.Columns[e.ColumnIndex].Name)
{}
}
Lub
PropertyInfo column = (new Process()).GetType().GetProperties().Where(x => x.Name == dgvProcessList.Columns[e.ColumnIndex].Name).First();
(Upewnij się, że nazwy kolumn pasują do Właściwości obiektu)
Cheers
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:27
Po wielu poszukiwaniach to mi pomogło:
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);
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-24 13:29:15
Możesz przekonwertować liczbę mnogą na liczbę mnogą.
items = items.AsQueryable().OrderBy("Name ASC");
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-30 10:29:40
Alternatywne rozwiązanie używa następującej klasy / interfejsu. Nie jest dynamiczny, ale działa.
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;
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-16 02:05:29
Ta odpowiedź jest odpowiedzią na komentarze, które wymagają przykładowego rozwiązania dostarczonego przez @ John Sheehan-Runscope
Proszę podać przykład dla reszty z nas.
W DAL (Warstwa dostępu do danych),
Wersja IEnumerable:
public IEnumerable<Order> GetOrders()
{
// i use Dapper to return IEnumerable<T> using Query<T>
//.. do stuff
return orders // IEnumerable<Order>
}
Wersja IQueryable
public IQueryable<Order> GetOrdersAsQuerable()
{
IEnumerable<Order> qry= GetOrders();
//use the built-in extension method AsQueryable in System.Linq namespace
return qry.AsQueryable();
}
Teraz możesz użyć wersji IQueryable do wiązania, na przykład GridView w Asp.net i korzyści dla sortowania (nie można sortować za pomocą IEnumerable version)
Użyłem Dapper jako ORM i zbudowałem IQueryable version i wykorzystałem sortowanie w GridView w asp.net to takie proste.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-23 20:34:58
Pierwsza Instalacja Dynamiczna Narzędzia -- > NuGet Package Manager -- > Package Manager Console
install-package System.Linq.Dynamic
Dodaj Przestrzeń Nazw using System.Linq.Dynamic;
Teraz możesz użyć OrderBy("Name, Age DESC")
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-10 18:31:00
Konwertuj listę do IEnumerable lub Iquerable, dodaj za pomocą systemu.LINQ.Dynamiczna przestrzeń nazw, następnie można wymienić nazwy właściwości w oddzielonym przecinkiem łańcuchu do metody OrderBy, która domyślnie pochodzi z systemu.LINQ.Dynamiczny.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-05 15:37:27
var result1 = lst.OrderBy(a=>a.Name);// for ascending order.
var result1 = lst.OrderByDescending(a=>a.Name);// for desc order.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-16 10:01:34