LINQ OrderBy dinamico su IEnumerable<T>/IQueryable<T>
-
09-06-2019 - |
Domanda
Ho trovato un esempio in Esempi di VS2008 per Dynamic LINQ che ti consente di utilizzare una stringa simile a SQL (ad es. OrderBy("Name, Age DESC"))
per ordinare.Sfortunatamente, il metodo incluso funziona solo su IQueryable<T>
;.Esiste un modo per attivare questa funzionalità IEnumerable<T>
?
Soluzione
Mi sono appena imbattuto in questo vecchio pezzo...
Per fare ciò senza la libreria dinamica LINQ, è sufficiente il codice come di seguito.Questo copre gli scenari più comuni, comprese le proprietà nidificate.
Per farlo funzionare IEnumerable<T>
potresti aggiungere alcuni metodi wrapper che passano AsQueryable
- ma il codice seguente è il nucleo Expression
logica necessaria.
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;
}
Modificare:diventa più divertente se vuoi mescolarlo dynamic
- anche se tienilo presente dynamic
si applica solo a LINQ-to-Objects (gli alberi delle espressioni per ORM ecc. non possono realmente rappresentare dynamic
interrogazioni - MemberExpression
non lo supporta).Ma ecco un modo per farlo con LINQ-to-Objects.Si noti che la scelta di Hashtable
è dovuto alla semantica di blocco favorevole:
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);
}
}
}
Altri suggerimenti
Troppo facile senza alcuna complicazione:
- Aggiungere
using System.Linq.Dynamic;
in cima. - Utilizzo
vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();
Ho trovato la risposta.Posso usare il .AsQueryable<>()
metodo di estensione per convertire il mio elenco in IQueryable, quindi eseguire l'ordine dinamico rispetto ad esso.
Mi sono appena imbattuto in questa domanda.
Utilizzando l'implementazione ApplyOrder di Marc dall'alto, ho messo insieme un metodo Extension che gestisce stringhe simili a SQL come:
list.OrderBy("MyProperty DESC, MyOtherProperty ASC");
I dettagli possono essere trovati qui: http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html
Immagino che funzionerebbe usare la riflessione per ottenere qualunque proprietà tu voglia ordinare:
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);
}
Tieni presente che l'utilizzo della riflessione è notevolmente più lento rispetto all'accesso diretto alla proprietà, quindi le prestazioni dovrebbero essere analizzate.
Basta basarsi su ciò che hanno detto gli altri.Ho scoperto che quanto segue funziona abbastanza bene.
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;
}
Ho inciampato questa domanda alla ricerca di clausole più ordinarie Linq e forse questo era quello che l'autore stava cercando
Ecco come farlo:
var query = pets.OrderBy(pet => pet.Name).ThenByDescending(pet => pet.Age);
Stavo provando a farlo ma ho avuto problemi con La soluzione di Kjetil Watnedal perché non utilizzo la sintassi linq inline: preferisco la sintassi in stile metodo.Il mio problema specifico era provare a eseguire l'ordinamento dinamico utilizzando un file custom IComparer
.
La mia soluzione è finita così:
Data una query IQueryable in questo modo:
List<DATA__Security__Team> teams = TeamManager.GetTeams();
var query = teams.Where(team => team.ID < 10).AsQueryable();
E dato un argomento del campo di ordinamento in fase di esecuzione:
string SortField; // Set at run-time to "Name"
L'OrderBy dinamico si presenta così:
query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField));
E questo utilizza un piccolo metodo di supporto chiamato GetReflectedPropertyValue():
public static string GetReflectedPropertyValue(this object subject, string field)
{
object reflectedValue = subject.GetType().GetProperty(field).GetValue(subject, null);
return reflectedValue != null ? reflectedValue.ToString() : "";
}
Un'ultima cosa: ho detto che volevo il OrderBy
utilizzare personalizzato IComparer
- perché volevo farlo Ordinamento naturale.
Per fare ciò, modifico semplicemente il file OrderBy
A:
query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField), new NaturalSortComparer<string>());
Vedere questo post per il codice per NaturalSortComparer()
.
Potresti aggiungerlo:
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
}
IL GetPropertyValue
la funzione proviene da La risposta di Kjetil Watnedal
Il problema sarebbe: perché?Qualsiasi tipo di questo tipo genererebbe eccezioni in fase di esecuzione, piuttosto che in fase di compilazione (come la risposta di D2VIANT).
Se hai a che fare con Linq to SQL e orderby è un albero delle espressioni, verrà comunque convertito in SQL per l'esecuzione.
Ecco qualcos'altro che ho trovato interessante.Se l'origine è un DataTable, puoi utilizzare l'ordinamento dinamico senza utilizzare 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;
riferimento: http://msdn.microsoft.com/en-us/library/bb669083.aspx (Utilizzando le estensioni DataSet)
Ecco un altro modo per farlo convertendolo in un DataView:
DataTable contacts = dataSet.Tables["Contact"];
DataView view = contacts.AsDataView();
view.Sort = "LastName desc, FirstName asc";
bindingSource1.DataSource = view;
dataGridView1.AutoResizeColumns();
Grazie a Maarten (Eseguire una query su una raccolta utilizzando l'oggetto PropertyInfo in LINQ) Ho ottenuto questa soluzione:
myList.OrderByDescending(x => myPropertyInfo.GetValue(x, null)).ToList();
Nel mio caso stavo lavorando su un "ColumnHeaderMouseClick" (WindowsForm) quindi ho appena trovato la colonna specifica premuta e il suo corrispondente PropertyInfo:
foreach (PropertyInfo column in (new Process()).GetType().GetProperties())
{
if (column.Name == dgvProcessList.Columns[e.ColumnIndex].Name)
{}
}
O
PropertyInfo column = (new Process()).GetType().GetProperties().Where(x => x.Name == dgvProcessList.Columns[e.ColumnIndex].Name).First();
(assicurati che i nomi delle colonne corrispondano alle proprietà dell'oggetto)
Saluti
Dopo molte ricerche, questo ha funzionato per me:
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);
}
È possibile convertire IEnumerable in IQueryable.
items = items.AsQueryable().OrderBy("Name ASC");
Una soluzione alternativa utilizza la seguente classe/interfaccia.Non è veramente dinamico, ma funziona.
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;
}
}
Questa risposta è una risposta ai commenti che richiedono un esempio per la soluzione fornita da @John Sheehan - Runscope
Per favore, fornisci un esempio per il resto di noi.
in DAL (livello di accesso ai dati),
La versione IEnumerable:
public IEnumerable<Order> GetOrders()
{
// i use Dapper to return IEnumerable<T> using Query<T>
//.. do stuff
return orders // IEnumerable<Order>
}
La versione IQueryable
public IQueryable<Order> GetOrdersAsQuerable()
{
IEnumerable<Order> qry= GetOrders();
//use the built-in extension method AsQueryable in System.Linq namespace
return qry.AsQueryable();
}
Ora puoi utilizzare la versione IQueryable per associare, ad esempio GridView in Asp.net e beneficiare dell'ordinamento (non puoi ordinare utilizzando la versione IEnumerable)
Ho usato Dapper come ORM e ho creato la versione IQueryable e ho utilizzato l'ordinamento in GridView in asp.net in modo così semplice.
Prima installa DynamicStrumenti --> Gestione pacchetti NuGet --> Console di gestione pacchetti
install-package System.Linq.Dynamic
Aggiungere Spazio dei nomi using System.Linq.Dynamic;
Ora puoi usare OrderBy("Name, Age DESC")
Converti List in IEnumerable o Iquerable, aggiungi utilizzando lo spazio dei nomi System.LINQ.Dynamic, quindi puoi menzionare i nomi delle proprietà in una stringa separata da virgole al metodo OrderBy che proviene per impostazione predefinita da System.LINQ.Dynamic.
Usa la dinamica linq
basta aggiungere using System.Linq.Dynamic;
E usalo in questo modo per ordinare tutte le tue colonne:
string sortTypeStr = "ASC"; // or DESC
string SortColumnName = "Age"; // Your column name
query = query.OrderBy($"{SortColumnName} {sortTypeStr}");
var result1 = lst.OrderBy(a=>a.Name);// for ascending order.
var result1 = lst.OrderByDescending(a=>a.Name);// for desc order.