我在VS2008的示例中找到了一个关于动态LINQ的例子,允许你使用类似SQL语句的字符串(例如OrderBy("Name, Age DESC"))
进行排序。不幸的是,该方法只适用于IQueryable<T>
。是否有办法在IEnumerable<T>
上实现这个功能呢?
我在VS2008的示例中找到了一个关于动态LINQ的例子,允许你使用类似SQL语句的字符串(例如OrderBy("Name, Age DESC"))
进行排序。不幸的是,该方法只适用于IQueryable<T>
。是否有办法在IEnumerable<T>
上实现这个功能呢?
刚刚偶然找到了这个老的代码...
如果不使用动态LINQ库,您只需要使用下面的代码即可实现此功能。这将包括大多数常见情况,包括嵌套属性。
为了让它与IEnumerable<T>
一起工作,您可以添加一些通过AsQueryable
进行的包装器方法 - 但是下面的代码是所需的核心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;
}
编辑:如果你想将它与dynamic
混合使用,这将更有趣 - 尽管请注意,dynamic
仅适用于LINQ-to-Objects(ORM等的表达式树实际上无法表示dynamic
查询 - MemberExpression
不支持它)。但是这里有一种使用LINQ-to-Objects的方法。请注意,选择Hashtable
是由于其有利的锁定语义:
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);
}
}
}
太容易了,没有任何复杂性:
using System.Linq.Dynamic;
。vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();
编辑:为节省时间,System.Linq.Dynamic.Core(System.Linq.Dynamic已弃用)程序集不是框架的一部分,但可以从nuget安装:System.Linq.Dynamic.Core
System.Linq.Dynamic
? - Rafael Herscovicivehicles.AsQueryable().OrderBy("Tire.Size").ToList();
,其中Tire
是属于Vehicle
的对象。 - neilsimp1刚好偶然看到这个问题。
使用Marc的上面提到的ApplyOrder实现,我组合了一个扩展方法,处理类似于SQL的字符串,例如:
list.OrderBy("MyProperty DESC, MyOtherProperty ASC");
详细信息可以在这里找到:http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html
我想使用反射来获取您想要排序的任何属性应该是可行的:
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);
}
请注意,使用反射比直接访问属性要慢得多,因此需要进行性能调查。
在其他人的基础上建立。我发现以下方法非常有效。
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;
}
我试着做这件事,但是在使用Kjetil Watnedal的解决方案时遇到了问题,因为我不使用内联linq语法 - 我更喜欢方法风格的语法。我的具体问题是尝试使用自定义IComparer
进行动态排序。
我的解决方案最终变成了这样:
给定一个IQueryable查询,如下所示:
List<DATA__Security__Team> teams = TeamManager.GetTeams();
var query = teams.Where(team => team.ID < 10).AsQueryable();
并且给定一个运行时排序字段参数:
string SortField; // Set at run-time to "Name"
动态的 OrderBy 如下所示:query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField));
这是使用一个名为 GetReflectedPropertyValue() 的小助手方法实现的:
public static string GetReflectedPropertyValue(this object subject, string field)
{
object reflectedValue = subject.GetType().GetProperty(field).GetValue(subject, null);
return reflectedValue != null ? reflectedValue.ToString() : "";
}
最后一件事 - 我提到我希望使用自定义的IComparer
来进行OrderBy
- 因为我想进行自然排序。
为了做到这一点,我只需修改OrderBy
如下:
query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField), new NaturalSortComparer<string>());
查看此帖子,获取NaturalSortComparer()
的代码。
我在寻找Linq多重排序子句时遇到了这个问题,也许这就是作者所寻找的。
以下是实现方法:
var query = pets.OrderBy(pet => pet.Name).ThenByDescending(pet => pet.Age);
在进行了大量搜索后,以下方法对我有效:
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);
}
使用动态 linq
只需添加 using System.Linq.Dynamic;
然后像这样使用它来对所有列进行排序:
string sortTypeStr = "ASC"; // or DESC
string SortColumnName = "Age"; // Your column name
query = query.OrderBy($"{SortColumnName} {sortTypeStr}");
首先安装动态工具 --> NuGet程序包管理器 --> 包管理器控制台
install-package System.Linq.Dynamic
添加 命名空间 using System.Linq.Dynamic;
现在你可以使用 OrderBy("Name, Age DESC")
IQueryable<T>
开始,所以如果你有像List<T>
(它是IEnumerable<T>
)这样的东西,你可能需要使用AsQueryable()
- 例如var sorted = someList.AsQueryable().OrderBy("Foo.Bar");
。注意不要改变原意。 - Marc Gravell