看着:
query.GroupBy(s => s.Country).Select(p => new
{
Value = p.Key,
Count = p.Count()
}
);
为了匹配IQueryable<Result> 的签名,你真正需要的是:
query.GroupBy(s => s.Country).Select(p => new
Result{
Value = p.Key,
Count = p.Count()
}
);
现在,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()});
}
好的。如何构建树。
我们将需要一个具有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()});
}
如果这个方法是内部的,那么上面的方法可能会过度杀戮,但如果它是公开的,那么如果你来调试它,额外的信息将是非常值得的。