根据参考资产的LINQ表达式, 我已经实施了Group By Extension,感谢Daniel Hilgarth的帮助,我需要帮助来扩展GroupByMany,如下所示
._unitOfWork.MenuSetRepository.Get()的GroupBy( "Role.Name", "MenuText");
扩展方法
public static IEnumerable<IGrouping<string, TElement>> GroupBy<TElement>(this IEnumerable<TElement> elements,string property)
{
var parameter = Expression.Parameter(typeof(TElement), "groupCol");
Expression<Func<TElement, string>> lambda;
if (property.Split('.').Count() > 1)
{
Expression body = null;
foreach (var propertyName in property.Split('.'))
{
Expression instance = body;
if (body == null)
instance = parameter;
body = Expression.Property(instance, propertyName);
}
lambda = Expression.Lambda<Func<TElement, string>>(body, parameter);
}
else
{
var menuProperty = Expression.PropertyOrField(parameter, property);
lambda = Expression.Lambda<Func<TElement, string>>(menuProperty, parameter);
}
var selector= lambda.Compile();
return elements.GroupBy(selector);
}
Run Code Online (Sandbox Code Playgroud)
这个答案由两部分组成:
IEnumerable<T>和IQueryable<T>两者之间的差异新要求并不像其他要求那样容易实现.主要原因是LINQ查询按复合键分组,导致在编译时创建匿名类型:
source.GroupBy(x => new { x.MenuText, Name = x.Role.Name })
Run Code Online (Sandbox Code Playgroud)
这导致了一类新的编译器生成的名称和两个属性MenuText和Name.
在运行时执行此操作是可能的,但实际上并不可行,因为它会将IL发送到新的动态程序集中.
对于我的解决方案,我选择了一种不同的方法:
因为所有涉及的属性似乎都是类型,string所以我们分组的键只是由分号分隔的属性值的串联.
因此,我们的代码生成的表达式等效于以下内容:
source.GroupBy(x => x.MenuText + ";" + x.Role.Name)
Run Code Online (Sandbox Code Playgroud)
实现此目的的代码如下所示:
private static Expression<Func<T, string>> GetGroupKey<T>(
params string[] properties)
{
if(!properties.Any())
throw new ArgumentException(
"At least one property needs to be specified", "properties");
var parameter = Expression.Parameter(typeof(T));
var propertyExpressions = properties.Select(
x => GetDeepPropertyExpression(parameter, x)).ToArray();
Expression body = null;
if(propertyExpressions.Length == 1)
body = propertyExpressions[0];
else
{
var concatMethod = typeof(string).GetMethod(
"Concat",
new[] { typeof(string), typeof(string), typeof(string) });
var separator = Expression.Constant(";");
body = propertyExpressions.Aggregate(
(x , y) => Expression.Call(concatMethod, x, separator, y));
}
return Expression.Lambda<Func<T, string>>(body, parameter);
}
private static Expression GetDeepPropertyExpression(
Expression initialInstance, string property)
{
Expression result = null;
foreach(var propertyName in property.Split('.'))
{
Expression instance = result;
if(instance == null)
instance = initialInstance;
result = Expression.Property(instance, propertyName);
}
return result;
}
Run Code Online (Sandbox Code Playgroud)
它的工作原理如下:
GetDeepPropertyExpression.这基本上是我在之前的回答中添加的代码.x => x.Role.Name如果已经传递了多个属性,我们将这些属性相互连接,并在其间连接一个分隔符,并将其用作lambda的主体.我选择了分号,但你可以使用你想要的任何东西.假设我们传递了三个属性("MenuText", "Role.Name", "ActionName"),那么结果看起来像这样:
x => string.Concat(
string.Concat(x.MenuText, ";", x.Role.Name), ";", x.ActionName)
Run Code Online (Sandbox Code Playgroud)
这与C#编译器为使用加号连接字符串的表达式生成的表达式相同,因此等效于:
x => x.MenuText + ";" + x.Role.Name + ";" + x.ActionName
Run Code Online (Sandbox Code Playgroud)您在问题中显示的扩展方法是一个非常糟糕的主意.
为什么?好吧,因为它的作用IEnumerable<T>.这意味着此group by 不在数据库服务器上执行,而是在应用程序的内存中本地执行.此外,所有后面的LINQ子句,如a Where也在内存中执行!
如果要提供扩展方法,则需要为两者IEnumerable<T>(在内存中,即LINQ to Objects)和IQueryable<T>(对于要在数据库上执行的查询,如LINQ to Entity Framework)执行此操作.
这与微软选择的方法相同.对于大多数LINQ扩展方法存在两个变种:一个适用于IEnumerable<T>与一个在工作IQueryable<T>,其生活在两个不同的类别Enumerable和Queryable.比较这些类中方法的第一个参数.
所以,你想要做的是这样的:
public static IEnumerable<IGrouping<string, TElement>> GroupBy<TElement>(
this IEnumerable<TElement> source, params string[] properties)
{
return source.GroupBy(GetGroupKey<TElement>(properties).Compile());
}
public static IQueryable<IGrouping<string, TElement>> GroupBy<TElement>(
this IQueryable<TElement> source, params string[] properties)
{
return source.GroupBy(GetGroupKey<TElement>(properties));
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3122 次 |
| 最近记录: |