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)
你会如何实现它?
看着:
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)
如果这个方法是内部的,那么上面可能会过度杀死,但是如果它是公开的,那么额外的信息将是值得的,如果你来调试它.