我正在使用LINQ to Objects聚合:
var summary = from esc in objs
where esc.time.Month == month && esc.time.Year == year
group esc by esc.rlf_id into g
select new {
ID = g.Key,
Total = g.Count(),
Preventable = g.Where(a => a.preventable).Count()
};
我的查询按预期工作,但我也想按匿名类型中的任意字段对查询进行排序。我发现LINQ: Order By Anonymous Type,但它在VB.NET中并且需要强烈指定要排序的字段。我可以用这个伪代码概念化我想要的东西:
query = get all esc in obj
aggregate into anonymous type with key ID
ID = g.Key, Total = g.Count, Preventable = g.Count of preventable
orderby inputField[0], (optional thenby inputField[1], ..., thenby inputField[n])
我如何完成:
我对点语法或查询语法持开放态度。
编辑: 有了Marko Stanojevic的回答,我能够部分满足我的要求。我不知道我能够像这样将LINQ方法链接在一起。我现在能够(并从中得到预期的结果):
var summary = from esc in objs
where esc.time.Month == month && esc.time.Year == year
group esc by esc.rlf_id into g
select new {
ID = g.Key,
Total = g.Count(),
Preventable = g.Where(a => a.preventable).Count()
};
summary = summary.OrderBy(e => e.Total);
我需要的是让我这样做的事情:(伪代码)
summary = summary.<Order/Then>By<(optional)Descending>(e => e.<someUserInput>)
给定一些指定用户想要排序的字符串,显然我可以这样做:
if (sortQuery.Equals("Total", StringComparison.OrdinalIgnoresCase), bool descending) {
summary = descending ? summary.OrderByDescending(e => e.Total) : summary.OrderBy(e => e.total)
} else if (sortQuery.Equals( /* ... etc */
然而,这最终会变得很丑陋,特别是因为我想将它用于(可能几乎无限的)越来越多的查询。它还需要考虑OrderBy()
与ThenBy()
的对比。
我希望我正在使用C#4,所以我现在可以使用动态...
答案 0 :(得分:5)
我不确定问题是什么。计算摘要后,您可以简单地
summary = summary.OrderBy(s => s.Total).ToList();
匿名类型的属性立即可见。如果你想传递它并遇到问题,最简单的解决方案是创建一个类,因为你知道它的样子。如果您因某些原因不想创建自定义类,我猜您可以使用Tuple
作为示例:
var summary = from esc in objs
where esc.time.Month == month && esc.time.Year == year
group esc by esc.rlf_id into g
select new Tuple<long, int, int> (
g.Key,
g.Count(),
g.Where(a => a.preventable).Count()
);
然后您可以按.Item1
或.Item2
等排序。不过,我会使用自定义类,因为它更清楚发生了什么。
答案 1 :(得分:0)
数据类型是匿名的这一事实并不重要,并且不会改变问题。匿名类型是另一种类型(它只有一个特殊名称)。与其他类型一样,它在编译时完全已知!您可以阅读Anonymous Type vs Dynamic Type以了解有关匿名和动态类型之间差异的更多信息。
难点在于调用方法(例如OrderBy
或OrderByDescending
)及其参数(例如keySelector item => item.MyFieldName
)仅在运行时才知道。
解决方案是使用反射。
下面的代码将OrderByRules
函数实现为一种扩展方法,适用于IQueryable<T>
类型的任何集合(因此,对于任何类型为IEnumerable<T>
的集合,仅使用AsQueryable<T>()
} operator。
第一条规则以特殊方式处理,以使用OrderBy
运算符而不是ThenBy
。然后递归处理其他的。
对sort运算符的调用在函数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 (升序)对集合进行排序。
答案 2 :(得分:0)
使用此extensions。
试试这个:
// Optional properties for dynamic sorting
var orders = new[]
{
new OrderByPropertyName {Desc = true, PropertyName = "Preventable"},
new OrderByPropertyName {Desc = false, PropertyName = "ID"},
new OrderByPropertyName {Desc = true, PropertyName = "Total"},
};
var firstOrder = orders.First();
var sortedSummary = firstOrder.Desc
? summary.AsQueryable().OrderByDescending(firstOrder.PropertyName)
: summary.AsQueryable().OrderBy(firstOrder.PropertyName);
foreach (var order in orders.Except(new[] {firstOrder}))
{
sortedSummary = order.Desc
? summary.OrderByDescending(order.PropertyName)
: summary.OrderBy(order.PropertyName);
}
var result = sortedSummary.ToList();