数据类型是匿名的这一事实并不重要,也不会改变问题。匿名类型是另一种类型(它只是有一个特殊的名称)。像其他类型一样,它在编译时就完全知道了!您可以阅读Anonymous Type vs Dynamic Type 了解更多关于匿名和动态类型之间的区别。
困难在于要调用的方法(例如OrderBy 或OrderByDescending)及其参数(例如keySelector item => item.MyFieldName)仅在运行时才知道。
解决方案是使用反射。
下面的代码将OrderByRules 函数作为扩展方法实现,该方法适用于IQueryable<T> 类型的任何集合(因此,只需使用AsQueryable<T>() 运算符,即可应用于任何IEnumerable<T> 类型的集合。
以特殊方式处理第一条规则以使用OrderBy 运算符而不是ThenBy。然后递归处理其他的。
对排序运算符的调用在函数OrderByFieldOrPropertyName 中执行。根据字段或属性反射信息,我们构造了一个 item => item.fieldName 形式的 lambda 表达式。 MakeGenericMethod 函数用于构造具体方法。基本上,它允许您从OrderBy<T> 切换到OrderBy<MyData>。
我希望这能回答你的问题。
/// <summary>
/// Express an order rule based on property name
/// </summary>
public class OrderRule
{
public OrderRule(string fieldOrPropertyName, bool descending)
{
FieldOrPropertyName = fieldOrPropertyName;
Descending = descending;
}
public string FieldOrPropertyName { get; private set; }
public bool Descending { get; private set; }
}
/// <summary>
/// Static class holding the OrderByRules extension method
/// </summary>
static public class MyLINQExtensions
{
/// <summary>
/// Order <paramref name="dataCollection"/> according to <paramref name="rules"/> sequence
/// </summary>
/// <typeparam name="T">Collection item type</typeparam>
/// <param name="dataCollection">Queryable collection</param>
/// <param name="rules">Order rules to apply</param>
/// <returns>Ordered queryable collection</returns>
public static IOrderedQueryable<T> OrderByRules<T>(this IQueryable<T> dataCollection, IEnumerable<OrderRule> rules)
{
if (!rules.Any())
throw new ArgumentException("Rules list is empty", "rules");
// apply first rule (special case: use OrderBy operator and not ThenBy)
OrderRule rule = rules.First();
MethodInfo orderOperator = rule.Descending ? OrderByDescendingMethodInfo : OrderByMethodInfo;
IOrderedQueryable<T> orderedDataCollection = OrderByFieldOrPropertyName(dataCollection, orderOperator, rule.FieldOrPropertyName);
// apply next rules recursivly
return OrderByRulesRecursivly(orderedDataCollection, rules.Skip(1).ToList());
}
static IOrderedQueryable<T> OrderByFieldOrPropertyName<T>(IQueryable<T> dataCollection, MethodInfo orderOperator, string fieldOrPropertyName)
{
// member corresponding to fieldOrPropertyName
MemberInfo memberInfo = typeof(T).GetField(fieldOrPropertyName);
Type memberType = null;
if (memberInfo == null)
memberInfo = typeof(T).GetProperty(fieldOrPropertyName);
else
memberType = (memberInfo as FieldInfo).FieldType;
if (memberInfo == null)
throw new ArgumentException(String.Format("Field or property '{0}' doesn't exist on type '{1}'", fieldOrPropertyName, typeof(T)));
else
memberType = (memberInfo as PropertyInfo).PropertyType;
// build lambda expression: item => item.fieldName
ParameterExpression paramExp = Expression.Parameter(typeof(T));
LambdaExpression keySelectorExp = Expression.Lambda(Expression.MakeMemberAccess(paramExp, memberInfo), paramExp);
// build concrete MethodInfo from the generic one
orderOperator = orderOperator.MakeGenericMethod(typeof(T), memberType);
// invoke method on dataCollection
return orderOperator.Invoke(null, new object[] {
dataCollection,
keySelectorExp
}) as IOrderedQueryable<T>;
}
static IOrderedQueryable<T> OrderByRulesRecursivly<T>(IOrderedQueryable<T> dataCollection, List<OrderRule> rules)
{
if (!rules.Any())
return dataCollection;
// apply first rule
OrderRule rule = rules.First();
MethodInfo orderOperator = rule.Descending ? ThenByDescendingMethodInfo : ThenByMethodInfo;
IOrderedQueryable<T> orderedDataCollection = OrderByFieldOrPropertyName(dataCollection, orderOperator, rule.FieldOrPropertyName);
// apply next rules recursivly
return OrderByRulesRecursivly(orderedDataCollection, rules.Skip(1).ToList());
}
/// <summary>
/// Static constructor. Initialize Reflection informations about Order operators
/// </summary>
static MyLINQExtensions()
{
// public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector);
OrderByMethodInfo = typeof(Queryable)
.GetMethods()
.First(m => m.Name == "OrderBy" && m.GetParameters().Count() == 2);
// public static IOrderedQueryable<TSource> OrderByDescending<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector);
OrderByDescendingMethodInfo = typeof(Queryable)
.GetMethods()
.First(m => m.Name == "OrderByDescending" && m.GetParameters().Count() == 2);
// public static IOrderedQueryable<TSource> ThenBy<TSource, TKey>(this IOrderedQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector);
ThenByMethodInfo = typeof(Queryable)
.GetMethods()
.First(m => m.Name == "ThenBy" && m.GetParameters().Count() == 2);
// public static IOrderedQueryable<TSource> ThenByDescending<TSource, TKey>(this IOrderedQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector);
ThenByDescendingMethodInfo = typeof(Queryable)
.GetMethods()
.First(m => m.Name == "ThenByDescending" && m.GetParameters().Count() == 2);
}
static MethodInfo OrderByMethodInfo;
static MethodInfo OrderByDescendingMethodInfo;
static MethodInfo ThenByMethodInfo;
static MethodInfo ThenByDescendingMethodInfo;
}
要编译,代码要求在标头中声明以下命名空间:
using System.Linq.Expressions;
using System.Reflection;
现在您可以在上下文中使用OrderByRules:
var summaryOrdered = summary.OrderByRules(new List<OrderRule> {
new OrderRule("Total", true),
new OrderRule("Preventable", false)
});
这将按Total(降序)和Preventable(升序)对集合进行排序。