【问题标题】:Expression.Call GroupBy then Select and Count()?Expression.Call GroupBy 然后选择和计数()?
【发布时间】:2016-01-27 02:25:09
【问题描述】:

使用表达式树,我需要以通用方式构建 GroupBy。 我要使用的静态方法如下:

public static IQueryable<Result> GroupBySelector<TSource>(this IQueryable<TSource> source, String coloumn)
{

  //Code here

}

Result 类有两个属性:

public string Value { get; set; }
public int Count { get; set; }

基本上我想通过表达式树构建以下 Linq 查询:

query.GroupBy(s => s.Country).Select(p => new 
                {
                    Value = p.Key,
                    Count = p.Count()
                }
            )

你会如何实现它?

【问题讨论】:

    标签: c# linq expression-trees


    【解决方案1】:

    看着:

    query.GroupBy(s => s.Country).Select(p => new 
      {
        Value = p.Key,
        Count = p.Count()
      }
    );
    

    为了匹配IQueryable&lt;Result&gt; 的签名,你真正需要的是:

    query.GroupBy(s => s.Country).Select(p => new 
      Result{
        Value = p.Key,
        Count = p.Count()
      }
    );
    

    现在,Select 可以按原样与任何IQueryable&lt;IGrouping&lt;string, TSource&gt;&gt; 一起使用。只有GroupBy需要我们使用表达式树。

    我们这里的任务是从一个类型和一个表示属性的字符串开始(它本身返回字符串)并创建一个Expression&lt;Func&lt;TSource, string&gt;&gt; 表示获取该属性的值。

    所以,让我们先生成方法的简单部分:

    public static IQueryable<Result> GroupBySelector<TSource>(this IQueryable<TSource> source, string column)
    {
        Expression<Func<TSource, string>> keySelector = //Build tree here.
    
        return source.GroupBy(keySelector).Select(p => new Result{Value = p.Key, Count = p.Count()});
    }
    

    好的。如何构建树。

    我们将需要一个具有TSource 类型参数的 lambda:

    var param = Expression.Parameter(typeof(TSource));
    

    我们需要获取名称与column匹配的属性:

    Expression.Property(param, column);
    

    而 lambda 中唯一需要的逻辑就是访问该属性:

    Expression<Func<TSource, string>> keySelector = Expression.Lambda<Func<TSource, string>>
    (
      Expression.Property(param, column),
      param
    );
    

    把它们放在一起:

    public static IQueryable<Result> GroupBySelector<TSource>(this IQueryable<TSource> source, String column)
    {
        var param = Expression.Parameter(typeof(TSource));
        Expression<Func<TSource, string>> keySelector = Expression.Lambda<Func<TSource, string>>
        (
            Expression.Property(param, column),
            param
        );
        return source.GroupBy(keySelector).Select(p => new Result{Value = p.Key, Count = p.Count()});
    }
    

    剩下的唯一事情就是异常处理,我通常不会将其包含在答案中,但其中有一部分值得关注。

    首先是明显的 null 和空检查:

    public static IQueryable<Result> GroupBySelector<TSource>(this IQueryable<TSource> source, String column)
    {
        if (source == null) throw new ArgumentNullException("source");
        if (column == null) throw new ArgumentNullException("column");
        if (column.Length == 0) throw new ArgumentException("column");
        var param = Expression.Parameter(typeof(TSource));
        Expression<Func<TSource, string>> keySelector = Expression.Lambda<Func<TSource, string>>
        (
            Expression.Property(param, column),
            param
        );
        return source.GroupBy(keySelector).Select(p => new Result{Value = p.Key, Count = p.Count()});
    }
    

    现在,让我们考虑如果我们为column 传递的字符串与TSource 的属性不匹配会发生什么。我们收到一个ArgumentException 和消息Instance property '[Whatever you asked for]' is not defined for type '[Whatever the type is]'. 在这种情况下,这几乎就是我们想要的,所以没问题。

    但是,如果我们传递了一个确实标识了一个属性但该属性不是string 类型的字符串,我们会得到类似"Expression of type 'System.Int32' cannot be used for return type 'System.String'" 的内容。这并不可怕,但也不是很好。让我们更明确一点:

    public static IQueryable<Result> GroupBySelector<TSource>(this IQueryable<TSource> source, String column)
    {
        if (source == null) throw new ArgumentNullException("source");
        if (column == null) throw new ArgumentNullException("column");
        if (column.Length == 0) throw new ArgumentException("column");
        var param = Expression.Parameter(typeof(TSource));
        var prop = Expression.Property(param, column);
        if (prop.Type != typeof(string)) throw new ArgumentException("'" + column + "' identifies a property of type '" + prop.Type + "', not a string property.", "column");
        Expression<Func<TSource, string>> keySelector = Expression.Lambda<Func<TSource, string>>
        (
            prop,
            param
        );
        return source.GroupBy(keySelector).Select(p => new Result{Value = p.Key, Count = p.Count()});
    }
    

    如果这个方法是内部的,那么上面的方法可能会过度杀戮,但如果它是公开的,那么如果你来调试它,额外的信息将是非常值得的。

    【讨论】:

    • 你有我的投票,我发布了一个非常相似的解决方案,它被无缘无故地否决了,但无论如何你的答案比我的好;)
    • 哇 - 这是一个完整的教程 :) +1
    • @IvanStoev 即使是乍看之下非常狭窄的问题也可以提供阐明的机会。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-02-11
    • 1970-01-01
    • 2020-12-19
    • 2018-06-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多