Ich habe ein Beispiel in der VS2008 Beispiele für Dynamic LINQ, mit dem Sie eine SQL-ähnliche Zeichenfolge verwenden können (z. B. OrderBy("Name, Age DESC"))
für die Bestellung. Leider funktioniert die enthaltene Methode nur bei IQueryable<T>
. Gibt es eine Möglichkeit, diese Funktionalität auf IEnumerable<T>
?
Antworten
Zu viele Anzeigen?Bin gerade über diesen Oldie gestolpert...
Um dies ohne die dynamische LINQ-Bibliothek zu tun, benötigen Sie nur den unten stehenden Code. Dies deckt die meisten gängigen Szenarien ab, einschließlich verschachtelter Eigenschaften.
Um es zum Laufen zu bringen mit IEnumerable<T>
können Sie einige Wrapper-Methoden hinzufügen, die über AsQueryable
- aber der folgende Code ist der Kern Expression
Logik erforderlich.
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: Noch lustiger wird es, wenn Sie das mit dynamic
- obwohl zu beachten ist, dass dynamic
gilt nur für LINQ-to-Objects (Ausdrucksbäume für ORMs usw. können nicht wirklich die dynamic
Rückfragen - MemberExpression
unterstützt es nicht). Aber hier ist eine Möglichkeit, es mit LINQ-to-Objects zu tun. Beachten Sie, dass die Wahl von Hashtable
ist auf die vorteilhafte Semantik der Sperren zurückzuführen:
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);
}
}
}
Zu einfach, zu komplikationslos:
- hinzufügen
using System.Linq.Dynamic;
an der Spitze. - Utilice
vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();
Editar : Um etwas Zeit zu sparen, wird die System.Linq.Dynamic.Core (System.Linq.Dynamic ist veraltet) Assembly ist nicht Teil des Frameworks, kann aber von Nuget installiert werden: System.Linq.Dynamic.Core
Ich bin gerade über diese Frage gestolpert.
Mit Marc's ApplyOrder-Implementierung von oben, schlug ich zusammen eine Erweiterung Methode, die SQL-ähnliche Zeichenfolgen wie behandelt:
list.OrderBy("MyProperty DESC, MyOtherProperty ASC");
Einzelheiten finden Sie hier: http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html
Ich schätze, es würde funktionieren, Reflexion 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 Reflexion wesentlich langsamer ist als der direkte Zugriff auf die Eigenschaft, so dass die Leistung untersucht werden müsste.
Ich schließe mich dem an, was andere gesagt haben. Ich habe festgestellt, dass das Folgende 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;
}
- See previous answers
- Weitere Antworten anzeigen