Dynamic LINQ OrderBy on IEnumerable
j'ai trouvé un exemple dans les VS2008 exemples pour LINQ dynamique qui vous permet d'utiliser une chaîne de type sql (par exemple OrderBy("Name, Age DESC"))
pour la commande. Malheureusement, la méthode ne comprenait que des travaux sur IQueryable<T>
;. Est-il possible d'obtenir cette fonctionnalité sur IEnumerable<T>
?
18 réponses
vient de tomber sur cette vieille...
pour faire cela sans la Bibliothèque dynamique LINQ, vous avez juste besoin du code comme ci-dessous. Cela couvre les scénarios les plus courants, y compris les propriétés imbriquées.
pour le faire fonctionner avec IEnumerable<T>
vous pouvez ajouter quelques méthodes d'enrubannage qui vont via AsQueryable
- mais le code ci-dessous est le noyau Expression
logique nécessaire.
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: il devient de plus en plus plaisir, si vous voulez mélanger avec dynamic
- à noter toutefois que la dynamic
s'applique uniquement à LINQ-to-Objets (expression-des arbres pour l'Orm, etc ne peut pas vraiment représenter dynamic
interroge - MemberExpression
ne le supporte pas). Mais voici un moyen de le faire avec LINQ-to-Objects. Notez que le choix de Hashtable
est dû à une sémantique de verrouillage favorable:
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);
}
}
}
trop facile sans aucune complication:
- ajouter
using System.Linq.Dynamic;
en haut. - Utiliser
vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();
j'ai trouvé la réponse. Je peux utiliser la méthode d'extension .AsQueryable<>()
pour convertir ma liste en IQueryable, puis exécuter l'ordre dynamique par contre.
vient de tomber sur cette question.
en utilisant L'implémentation ApplyOrder de Marc d'en haut, j'ai assemblé une méthode D'Extension qui gère les chaînes de type SQL comme:
list.OrderBy("MyProperty DESC, MyOtherProperty ASC");
détails ici: http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html
je suppose qu'il serait utile d'utiliser la réflexion pour obtenir n'importe quelle propriété que vous voulez trier sur:
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);
}
notez que l'utilisation de la réflexion est beaucoup plus lente que l'accès direct à la propriété, de sorte que la performance devrait être étudiée.
Juste en appuyant sur ce que les autres ont dit. J'ai trouvé que ce qui suit fonctionne assez bien.
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;
}
j'ai trébuché cette question à la recherche de Linq clauses orderby multiples et peut-être que c'est ce que l'auteur cherchait
Voici comment faire:
var query = pets.OrderBy(pet => pet.Name).ThenByDescending(pet => pet.Age);
j'essayais de le faire mais j'ai des problèmes avec solution de Kjetil Watnedal parce que je n'utilise pas la syntaxe linq inline - je préfère la syntaxe de style de méthode. Mon problème spécifique était d'essayer de faire du tri dynamique en utilisant un IComparer
personnalisé .
ma solution a fini comme ceci:
donne une requête IQueryable comme ceci:
List<DATA__Security__Team> teams = TeamManager.GetTeams();
var query = teams.Where(team => team.ID < 10).AsQueryable();
Et donné un champ de tri argument:
string SortField; // Set at run-time to "Name"
La dynamique OrderBy ressemble à ceci:
query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField));
et c'est en utilisant une petite méthode d'aide appelée GetReflectedPropertyValue ():
public static string GetReflectedPropertyValue(this object subject, string field)
{
object reflectedValue = subject.GetType().GetProperty(field).GetValue(subject, null);
return reflectedValue != null ? reflectedValue.ToString() : "";
}
une dernière chose - j'ai mentionné que je voulais que le OrderBy
pour utiliser la coutume IComparer
- parce que je voulais faire tri naturel .
OrderBy
à:
query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField), new NaturalSortComparer<string>());
Voir ce post le code NaturalSortComparer()
.
vous pouvez l'ajouter:
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
}
la fonction GetPropertyValue
est de réponse de Kjetil Watnedal
La question serait pourquoi? Une telle sorte lancerait des exceptions au moment de l'exécution, plutôt que de compiler le temps (comme la réponse de D2VIANT).
si vous avez affaire à Linq en Sql et que l'orderby est un arbre d'expression, il sera converti en SQL pour exécution de toute façon.
voilà autre chose que j'ai trouvé intéressant. Si votre source est une DataTable, vous pouvez utiliser le tri dynamique sans utiliser la ligne dynamique 151930920"
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;
référence: http://msdn.microsoft.com/en-us/library/bb669083.aspx (utilisant les extensions des données)
Voici une autre façon de le faire en le convertissant en une DataView:
DataTable contacts = dataSet.Tables["Contact"];
DataView view = contacts.AsDataView();
view.Sort = "LastName desc, FirstName asc";
bindingSource1.DataSource = view;
dataGridView1.AutoResizeColumns();
merci à Maarten ( interrogation d'une collection en utilisant L'objet PropertyInfo dans LINQ ) j'ai obtenu cette solution:
myList.OrderByDescending(x => myPropertyInfo.GetValue(x, null)).ToList();
dans mon cas je travaillais sur un "ColumnHeaderMouseClick" (WindowsForm) donc je viens de trouver la colonne spécifique pressée et son correspondant PropertyInfo:
foreach (PropertyInfo column in (new Process()).GetType().GetProperties())
{
if (column.Name == dgvProcessList.Columns[e.ColumnIndex].Name)
{}
}
ou
PropertyInfo column = (new Process()).GetType().GetProperties().Where(x => x.Name == dgvProcessList.Columns[e.ColumnIndex].Name).First();
(assurez-vous que les noms de vos colonnes correspondent aux propriétés de l'objet)
Cheers
après beaucoup de recherches cela a fonctionné pour moi:
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);
}
vous pouvez convertir le IEnumerable en IQueryable.
items = items.AsQueryable().OrderBy("Name ASC");
une solution alternative utilise la classe/interface suivante. Ce n'est pas vraiment dynamique, mais il fonctionne.
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;
}
}
Cette réponse est une réponse aux commentaires qui ont besoin d'un exemple pour la solution fournie par @John Sheehan - Runscope
, Veuillez fournir un exemple pour le reste d'entre nous.
in DAL (Data Access Layer),
L'interface IEnumerable version:
public IEnumerable<Order> GetOrders()
{
// i use Dapper to return IEnumerable<T> using Query<T>
//.. do stuff
return orders // IEnumerable<Order>
}
Le IQueryable version
public IQueryable<Order> GetOrdersAsQuerable()
{
IEnumerable<Order> qry= GetOrders();
//use the built-in extension method AsQueryable in System.Linq namespace
return qry.AsQueryable();
}
Maintenant vous pouvez utiliser la version IQueryable pour lier, par exemple GridView dans Asp.net et l'avantage pour le tri (vous ne pouvez pas trier en utilisant la version IEnumerable)
j'ai utilisé Dapper comme ORM et construire la version IQueryable et utilisé le tri dans GridView en asp.net si facile.
Première Installation Dynamique Outils --> Gestionnaire De Package NuGet --> Gestionnaire De Paquets De La Console
install-package System.Linq.Dynamic
Ajouter Espace De Noms using System.Linq.Dynamic;
Maintenant vous pouvez utiliser OrderBy("Name, Age DESC")
convertir la liste en IEnumerable ou Iquerable, ajouter en utilisant le système.LINQ.Dynamique de l'espace de noms, alors u peut citer les noms de propriété de séparation par virgule chaîne de OrderBy Méthode qui vient par défaut du Système.LINQ.Dynamique.
var result1 = lst.OrderBy(a=>a.Name);// for ascending order.
var result1 = lst.OrderByDescending(a=>a.Name);// for desc order.