匿名类型中的字段排序 [英] Order by fields in an anonymous type

查看:86
本文介绍了匿名类型中的字段排序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用LINQ to Objects进行聚合:

I am using LINQ to Objects to aggregate:

  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:按匿名类型订购,但是它在VB.NET中并且要求严格指定要排序的字段.我可以概念化此伪代码要实现的内容:

My query works as I would expect, but I also want to order the query by arbitrary field(s) in the anonymous type. I found LINQ: Order By Anonymous Type, but it's in VB.NET and requires strongly specifying what field to sort by. I can conceptualize what I want to accompilish with this pseudocode:

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])

我如何完成:

  • 在LINQ查询中按匿名类型的字段排序 (完成-谢谢Marko!)
  • 按匿名类型的任意字段进一步排序

我愿意接受点语法或查询语法.

I am open to dot syntax or query syntax.

有了Marko Stanojevic的回答,我就能部分满足我的要求.我没有意识到我能够像这样将LINQ方法链接在一起.现在,我可以(并从中获得预期的结果):

With Marko Stanojevic's answer, I am able to partially meet my requirements. I was unaware that I am able to chain LINQ methods together like that. I now am able to (and get expected results from):

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); 

我需要让我做的事情:(伪代码)

What I need is something that lets me do: (pseudocode)

summary = summary.<Order/Then>By<(optional)Descending>(e => e.<someUserInput>)

给出一些指定用户希望如何排序的字符串,显然我可以做到:

Given some string that specifies how the user wants to sort, obviously I can do:

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().

However, this ends up being ugly quick-like, especially because I would like to use this for a (potentially nearly infinite) growing number of queries. It would also need to account for OrderBy() versus ThenBy().

我希望我正在使用C#4,以便现在就可以使用动态...

I wish I was working with C# 4 so I could use dynamic right about now...

推荐答案

数据类型为匿名的事实并不重要,也不会改变问题.匿名类型是另一种类型(它只有一个特殊名称).与其他类型一样,它在编译时是完全已知的!您可以阅读匿名类型与动态类型,以详细了解匿名类型和动态类型之间的区别

The fact that the data type is anonymous is not important and does not change the problem. An anonymous type is a type as another (it just has a special name). Like other types it is completely known at compile time! You can read Anonymous Type vs Dynamic Type to learn more about difference between anonymous and dynamic types.

困难在于,仅在运行时才知道要调用的方法(例如OrderByOrderByDescending)及其参数(例如keySelector item => item.MyFieldName).

The difficulty is that the methods (eg OrderBy or OrderByDescending) to invoke and their parameters (eg the keySelector item => item.MyFieldName) are known only at runtime.

解决方案是使用反射.

下面的代码将OrderByRules功能实现为扩展方法,该扩展方法适用于任何IQueryable<T>类型的集合(因此,只需使用AsQueryable<T>()运算符即可应用于任何IEnumerable<T>类型的集合.

The code below implements the OrderByRules function as an extension method that applies to any collection of type IQueryable<T> (So, to any collection of type IEnumerable<T> using simply the AsQueryable<T>() operator.

以特殊方式处理第一条规则,以使用OrderBy运算符而不是ThenBy.然后其余的递归处理.

The first rule is processed in a special way to use the OrderBy operator and not ThenBy. Then the others are processed recursively.

对排序运算符的调用是在功能OrderByFieldOrPropertyName中执行的.根据字段或属性反射信息,我们构造形式为item => item.fieldName的lambda表达式. MakeGenericMethod函数用于构造具体方法.基本上,它允许您从OrderBy<T>切换到OrderBy<MyData>.

The call to sort operator is performed in the function OrderByFieldOrPropertyName. From the field or property reflection information, we construct a lambda expression of the form item => item.fieldName. The MakeGenericMethod function is used to construct the concrete method. Basically, it allows you to switch from OrderBy<T> to OrderBy<MyData>.

我希望能回答您的问题.

I hope that answers your question.

/// <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;
}

要进行编译,代码要求在标题中声明以下命名空间:

To be compiled, the code requires that the following namespaces are declared in the header:

using System.Linq.Expressions;
using System.Reflection;

现在您可以在上下文中使用OrderByRules:

Now you can use OrderByRules on your context:

var summaryOrdered = summary.OrderByRules(new List<OrderRule> {
    new OrderRule("Total", true),
    new OrderRule("Preventable", false)
});

这将按 Total (降序)然后按 Preventable (升序)对集合进行排序.

This will order the collection by Total (descending) and then by Preventable (ascending).

这篇关于匿名类型中的字段排序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆