【问题标题】:Order by fields in an anonymous type按匿名类型中的字段排序
【发布时间】:2014-03-15 20:09:24
【问题描述】:

我正在使用 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])

如何完成:

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

我对点语法或查询语法持开放态度。

编辑: 有了 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,所以我现在可以使用动态...

【问题讨论】:

  • if(..) query = query.OrderBy(..); if(..) query = query.ThenBy(..);
  • 如果有任何帮助,您始终可以在 group 之前执行 orderby
  • 我同意乔治的观点。在进行分组之前订购摘要,然后应用您的选择。
  • @kryptonkal 详细说明?当然,我可以通过键订购(因为那时可用),但 Total 和 Preventable 字段不可用;
  • 抱歉,我错过了。我只按 ID 读取订单。

标签: c# linq c#-3.0 linq-to-objects


【解决方案1】:

我不确定是什么问题。计算汇总后,您可以简单地

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 等排序。不过,我会使用自定义类,因为它更清楚发生了什么。

【讨论】:

  • 为了清晰起见,我正在考虑一个模型类,谢谢你的帖子
【解决方案2】:

数据类型是匿名的这一事实并不重要,也不会改变问题。匿名类型是另一种类型(它只是有一个特殊的名称)。像其他类型一样,它在编译时就完全知道了!您可以阅读Anonymous Type vs Dynamic Type 了解更多关于匿名和动态类型之间的区别。

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

解决方案是使用反射

下面的代码将OrderByRules 函数作为扩展方法实现,该方法适用于IQueryable&lt;T&gt; 类型的任何集合(因此,只需使用AsQueryable&lt;T&gt;() 运算符,即可应用于任何IEnumerable&lt;T&gt; 类型的集合。

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

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

我希望这能回答你的问题。

/// <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(升序)对集合进行排序。

【讨论】:

  • 这太疯狂了。为什么不直接使用 .OrderBy / .OrderByDescending 扩展方法? -- 它们适用于匿名类型。
  • @BrainSlug83,问题与匿名类型无关,而是仅在运行时知道的顺序规则。 @jdphenix 伪代码:summary = summary.&lt;Order/Then&gt;By&lt;(optional)Descending&gt;(e =&gt; e.&lt;someUserInput&gt;)
【解决方案3】:

您可以使用以下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();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-03-15
    • 1970-01-01
    • 2015-05-24
    • 2012-04-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-01
    相关资源
    最近更新 更多