Expression.Call GroupBy然后选择和Count()?

Fra*_*sco 1 c# linq expression-trees

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

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

  //Code here

}
Run Code Online (Sandbox Code Playgroud)

Result类有两个属性:

public string Value { get; set; }
public int Count { get; set; }
Run Code Online (Sandbox Code Playgroud)

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

query.GroupBy(s => s.Country).Select(p => new 
                {
                    Value = p.Key,
                    Count = p.Count()
                }
            )
Run Code Online (Sandbox Code Playgroud)

你会如何实现它?

Jon*_*nna 5

看着:

query.GroupBy(s => s.Country).Select(p => new 
  {
    Value = p.Key,
    Count = p.Count()
  }
);
Run Code Online (Sandbox Code Playgroud)

要匹配IQueryable<Result>您实际需要的签名是:

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

现在,它Select可以与任何IQueryable<IGrouping<string, TSource>>原样一起工作.只有GroupBy这需要我们使用表达式树.

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

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

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()});
}
Run Code Online (Sandbox Code Playgroud)

好的.如何构建树.

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

var param = Expression.Parameter(typeof(TSource));
Run Code Online (Sandbox Code Playgroud)

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

Expression.Property(param, column);
Run Code Online (Sandbox Code Playgroud)

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

Expression<Func<TSource, string>> keySelector = Expression.Lambda<Func<TSource, string>>
(
  Expression.Property(param, column),
  param
);
Run Code Online (Sandbox Code Playgroud)

把它们放在一起:

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()});
}
Run Code Online (Sandbox Code Playgroud)

关于唯一剩下的是异常处理,我通常不会在答案中包含,但其中一部分值得关注.

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

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()});
}
Run Code Online (Sandbox Code Playgroud)

现在,让我们考虑一下如果我们传递一个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()});
}
Run Code Online (Sandbox Code Playgroud)

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