使用表达式树,我需要以通用方式构建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()
}
)
你会如何实现它?
答案 0 :(得分:5)
看着:
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()});
}
关于唯一剩下的就是异常处理,我通常不会在答案中包含,但其中一部分值得关注。
首先是明显的空和空检查:
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()});
}
如果这个方法是内部的,那么上面的内容可能会被过度杀死,但是如果它是公开的,那么如果你来调试它,那么额外的信息将非常值得。