Dynamisches LINQ OrderBy auf IEnumerable<T> / IQueryable<T>
-
09-06-2019 - |
Frage
Ein Beispiel habe ich im gefunden VS2008-Beispiele für Dynamic LINQ, mit dem Sie eine SQL-ähnliche Zeichenfolge verwenden können (z. B. OrderBy("Name, Age DESC"))
zum Bestellen.Leider funktioniert die enthaltene Methode nur auf IQueryable<T>
;.Gibt es eine Möglichkeit, diese Funktionalität zu aktivieren? IEnumerable<T>
?
Lösung
Bin gerade über diesen Oldie gestolpert ...
Um dies ohne die dynamische LINQ-Bibliothek zu tun, benötigen Sie lediglich den folgenden Code.Dies deckt die gängigsten Szenarien ab, einschließlich verschachtelter Eigenschaften.
Damit es funktioniert IEnumerable<T>
Sie könnten einige Wrapper-Methoden hinzufügen, die über ausgeführt werden AsQueryable
- aber der Code unten ist der Kern Expression
Logik nötig.
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;
}
Bearbeiten:Es macht mehr Spaß, wenn man das damit mischen möchte dynamic
- obwohl beachten Sie das dynamic
Gilt nur für LINQ-to-Objects (Ausdrucksbäume für ORMs usw. können nicht wirklich dargestellt werden dynamic
Anfragen - MemberExpression
unterstützt es nicht).Aber hier ist eine Möglichkeit, dies mit LINQ-to-Objects zu tun.Beachten Sie, dass die Auswahl von Hashtable
liegt an der günstigen Sperrsemantik:
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);
}
}
}
Andere Tipps
Zu einfach und ohne Komplikationen:
- Hinzufügen
using System.Linq.Dynamic;
oben. - Verwenden
vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();
Ich habe die Antwort gefunden.Ich kann das verwenden .AsQueryable<>()
Erweiterungsmethode, um meine Liste in IQueryable zu konvertieren, und führe dann die dynamische Reihenfolge dagegen aus.
Bin gerade über diese Frage gestolpert.
Unter Verwendung der ApplyOrder-Implementierung von Marc von oben habe ich eine Erweiterungsmethode zusammengestellt, die SQL-ähnliche Zeichenfolgen verarbeitet wie:
list.OrderBy("MyProperty DESC, MyOtherProperty ASC");
Einzelheiten finden Sie hier: http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html
Ich denke, es würde funktionieren, Reflektion zu verwenden, um die Eigenschaft zu erhalten, nach der Sie sortieren möchten:
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);
}
Beachten Sie, dass die Verwendung von Reflektion erheblich langsamer ist als der direkte Zugriff auf die Eigenschaft, daher müsste die Leistung untersucht werden.
Ich baue einfach auf dem auf, was andere gesagt haben.Ich habe festgestellt, dass Folgendes ganz gut funktioniert.
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;
}
Ich stolpere diese Frage auf der Suche nach linq mehrere Orderby -Klauseln und vielleicht war es das, wonach der Autor suchte
So geht's:
var query = pets.OrderBy(pet => pet.Name).ThenByDescending(pet => pet.Age);
Ich habe versucht, dies zu tun, hatte aber Probleme damit Kjetil Watnedals Lösung Da ich nicht die Inline-Linq-Syntax verwende, bevorzuge ich die Syntax im Methodenstil.Mein spezifisches Problem bestand darin, eine dynamische Sortierung mithilfe einer benutzerdefinierten Methode durchzuführen IComparer
.
Meine Lösung endete so:
Gegeben sei eine IQueryable-Abfrage wie folgt:
List<DATA__Security__Team> teams = TeamManager.GetTeams();
var query = teams.Where(team => team.ID < 10).AsQueryable();
Und wenn ein Sortierfeldargument zur Laufzeit gegeben ist:
string SortField; // Set at run-time to "Name"
Das dynamische OrderBy sieht folgendermaßen aus:
query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField));
Und das mithilfe einer kleinen Hilfsmethode namens GetReflectedPropertyValue():
public static string GetReflectedPropertyValue(this object subject, string field)
{
object reflectedValue = subject.GetType().GetProperty(field).GetValue(subject, null);
return reflectedValue != null ? reflectedValue.ToString() : "";
}
Eine letzte Sache: Ich habe erwähnt, dass ich das wollte OrderBy
benutzerdefiniert verwenden IComparer
- weil ich es tun wollte Natürliche Sortierung.
Dazu ändere ich einfach die OrderBy
Zu:
query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField), new NaturalSortComparer<string>());
Sehen dieser Beitrag für den Code für NaturalSortComparer()
.
Sie könnten es hinzufügen:
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
}
Der GetPropertyValue
Funktion ist von Kjetil Watnedals Antwort
Die Frage wäre: Warum?Eine solche Sortierung würde Ausnahmen zur Laufzeit und nicht zur Kompilierungszeit auslösen (wie die Antwort von D2VIANT).
Wenn Sie mit Linq to Sql arbeiten und orderby ein Ausdrucksbaum ist, wird er zur Ausführung trotzdem in SQL konvertiert.
Hier ist noch etwas, das ich interessant fand.Wenn Ihre Quelle eine DataTable ist, können Sie die dynamische Sortierung verwenden, ohne Dynamic Linq zu verwenden
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;
Referenz: http://msdn.microsoft.com/en-us/library/bb669083.aspx (Verwenden von DataSetExtensions)
Hier ist eine weitere Möglichkeit, dies zu tun, indem Sie es in eine DataView konvertieren:
DataTable contacts = dataSet.Tables["Contact"];
DataView view = contacts.AsDataView();
view.Sort = "LastName desc, FirstName asc";
bindingSource1.DataSource = view;
dataGridView1.AutoResizeColumns();
Danke an Maarten (Fragen Sie eine Sammlung mithilfe des PropertyInfo-Objekts in LINQ ab) Ich habe diese Lösung:
myList.OrderByDescending(x => myPropertyInfo.GetValue(x, null)).ToList();
In meinem Fall habe ich an einem „ColumnHeaderMouseClick“ (WindowsForm) gearbeitet, also habe ich gerade die spezifische gedrückte Spalte und die entsprechende PropertyInfo gefunden:
foreach (PropertyInfo column in (new Process()).GetType().GetProperties())
{
if (column.Name == dgvProcessList.Columns[e.ColumnIndex].Name)
{}
}
ODER
PropertyInfo column = (new Process()).GetType().GetProperties().Where(x => x.Name == dgvProcessList.Columns[e.ColumnIndex].Name).First();
(Stellen Sie sicher, dass Ihre Spaltennamen mit den Objekteigenschaften übereinstimmen.)
Prost
Nach langem Suchen hat das bei mir funktioniert:
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);
}
Sie können IEnumerable in IQueryable konvertieren.
items = items.AsQueryable().OrderBy("Name ASC");
Eine alternative Lösung verwendet die folgende Klasse/Schnittstelle.Es ist nicht wirklich dynamisch, aber es funktioniert.
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;
}
}
Diese Antwort ist eine Antwort auf die Kommentare, die ein Beispiel für die von bereitgestellte Lösung benötigen @John Sheehan – Runscope
Bitte geben Sie ein Beispiel für den Rest von uns.
in DAL (Data Access Layer),
Die IEnumerable-Version:
public IEnumerable<Order> GetOrders()
{
// i use Dapper to return IEnumerable<T> using Query<T>
//.. do stuff
return orders // IEnumerable<Order>
}
Die IQueryable-Version
public IQueryable<Order> GetOrdersAsQuerable()
{
IEnumerable<Order> qry= GetOrders();
//use the built-in extension method AsQueryable in System.Linq namespace
return qry.AsQueryable();
}
Jetzt können Sie die IQueryable-Version zum Binden verwenden, beispielsweise GridView in Asp.net, und von der Sortierung profitieren (Sie können nicht mit der IEnumerable-Version sortieren).
Ich habe Dapper als ORM verwendet und eine IQueryable-Version erstellt und die Sortierung in GridView in asp.net ganz einfach genutzt.
Installieren Sie zunächst DynamicExtras -> NuGet-Paketmanager -> Paket-Manager-Konsole
install-package System.Linq.Dynamic
Hinzufügen Namensraum using System.Linq.Dynamic;
Jetzt können Sie es verwenden OrderBy("Name, Age DESC")
Konvertieren Sie die Liste in IEnumerable oder Iquerable, fügen Sie sie mithilfe des System.LINQ.Dynamic-Namespace hinzu. Anschließend können Sie die Eigenschaftsnamen in einer durch Kommas getrennten Zeichenfolge in der OrderBy-Methode angeben, die standardmäßig aus System.LINQ.Dynamic stammt.
Verwenden Sie Dynamik linq
einfach hinzufügen using System.Linq.Dynamic;
Und verwenden Sie es so, um alle Ihre Spalten zu ordnen:
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.