【发布时间】:2017-07-16 15:39:35
【问题描述】:
我创建了一个通用方法,它接受一个标识分组键的成员访问表达式,就像传递给IQueryable<T>.GroupBy 一样。
private static IQueryable<ObjectWithRank<T>> IncludeBestRankPerGroup<T,TGroupKey>(this IQueryable<T> q, Expression<Func<T, TGroupKey>> keySelector)
class ObjectWithRank<T> {
public T RankedObject { get; set; }
public int Rank { get; set; }
}
IncludeBestRankPerGroup 方法是我的IncludeRank 方法的一种变体,它只采用IQueryable<T> 并通过将每个元素包装在ObjectWithRank<T> 中来对每个元素应用等级,返回一个IQueryable<ObjectWithRank<T>>。然后我想按keySelector 分组并选择每组中排名最高的元素。
这需要我将 lambda 表达式从形式 1 转换为 2,以便将其传递给 IQueryable<ObjectWithRank<T>>.GroupBy:
(T x) => x.GroupingProperty(ObjectWithRank<T> x) => x.RankedObject.GroupingProperty
请注意,我不能只将keySelector 的根对象类型从T 更改为ObjectWithRank<T>,因为ObjectWithRank<T> 类未在调用IncludeBestRankPerGroup 的公共方法中公开。 API 的用户只提供了一个IQueryable<T>,然后收到了一个IQueryable<T>,其中包含每个组中排名最高的项目,因此他们永远不会看到ObjectWithRank<T> 在幕后使用。
我设法使用以下代码执行了转换,但它仅适用于简单的成员访问表达式。例如,它可以将x => x.GroupingKey 之类的表达式转换为x => x.RankedObject.GroupingKey,但它不适用于两级深度成员访问表达式,我必须将x => x.SubObject.GroupingKey 之类的内容转换为x => x.RankedObject.SubObject.GroupingKey。
private static Expression<Func<ObjectWithRank<T>, TGroupKey>> RebuildMemberAccessForRankedObject<T, TGroupBy>(Expression<Func<T, TGroupKey>> keySelector)
{
Expression<Func<ObjectWithRank<T>, T>> objectAccessExpression = x => x.RankedObject;
return Expression.Lambda<Func<ObjectWithRank<T>, TGroupKey>>(
Expression.Property(objectAccessExpression.Body, (keySelector.Body as MemberExpression).Member as PropertyInfo)
, objectAccessExpression.Parameters
);
}
上面看起来像是一个 hack,我首先创建了一个访问 ObjectWithRank<T> 的 T RankedObject 属性的成员访问表达式,然后添加提供的 keySelector 成员访问表达式。我不确定是否有一种简单的方法可以让它工作。似乎 Expression.Property 一次只允许向下钻取一个属性,所以也许我需要某种循环来从顶部重建表达式,一次向下钻取一个属性。
这里有一个类似的问题,它确实有一个简单的解决方案,但在表达式的另一端更深一层,这不是我想要做的。 Alter Lambda Expression to go one level deeper
【问题讨论】:
标签: c# lambda linq-to-entities expression