我创建了一个通用方法,它接受一个标识分组键的成员访问表达式,就像传递给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>
中并对每个元素应用排名,并返回{ {1}}。然后我想按IQueryable<ObjectWithRank<T>>
进行分组,并选择每组最佳排名元素。
这要求我将lambda表达式从表单1转换为2,这样我就可以将它传递给keySelector
:
IQueryable<ObjectWithRank<T>>.GroupBy
(T x) => x.GroupingProperty
请注意,我不能只将(ObjectWithRank<T> x) => x.RankedObject.GroupingProperty
的根对象类型从keySelector
更改为T
,因为ObjectWithRank<T>
类未在调用的公共方法中公开ObjectWithRank<T>
。 API的用户只提供IncludeBestRankPerGroup
,并收到每个组中排名最高的项目IQueryable<T>
,因此他们永远不会看到IQueryable<T>
在幕后使用。
我设法使用以下代码执行转换,但它仅适用于简单的成员访问表达式。例如,它可以将ObjectWithRank<T>
这样的表达式转换为x => x.GroupingKey
,但它不能使用两级深层成员访问表达式,我必须转换{{} {1}}至x => x.RankedObject.GroupingKey
。
x => x.SubObject.GroupingKey
上面看起来像是一个黑客,我首先创建一个访问x => x.RankedObject.SubObject.GroupingKey
的{{1}}属性的成员访问表达式,然后添加提供的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
);
}
成员访问表达式。我不确定是否有一种简单的方法可以让它发挥作用。似乎Expression.Property只允许一次向下钻取一个属性,所以我可能需要某种循环来从顶部重建表达式,一次向下钻取一个属性。
这里有一个类似的问题确实有一个简单的解决方案,但在表达的另一端更深层次,这不是我想要做的事情。 Alter Lambda Expression to go one level deeper
答案 0 :(得分:0)
我能够用一个在成员表达式中向下钻取的递归lamba替换表达式的根,直到它到达参数表达式,用最新级别的新根表达式替换参数表达式,然后展开调用堆栈用更新的内部表达式替换每个成员表达式的Expression一直回到顶部,然后使用为新根设置的更新表达式和参数表达式创建一个新的lambda。
private static Expression<Func<TInNew, TOut>> UpdateExpressionRoot<TOut, TInOld, TInNew>(Expression<Func<TInNew, TInOld>> newRoot, Expression<Func<TInOld, TOut>> memberAccess)
{
Func<MemberExpression, MemberExpression> updateDeepestExpression = null;
updateDeepestExpression = e =>
{
if (e.Expression is MemberExpression)
{
var updatedChild = updateDeepestExpression((MemberExpression)e.Expression);
return e.Update(updatedChild);
}
if (e.Expression is ParameterExpression)
return e.Update(newRoot.Body);
throw new ArgumentException("Member access expression must be composed of nested member access expressions only.", nameof(memberAccess));
};
return Expression.Lambda<Func<TInNew, TOut>>(updateDeepestExpression(memberAccess.Body as MemberExpression), newRoot.Parameters);
}
可以像这样调用:
class Car
{
Manufacturer Manufacturer { get; set; }
}
class Manufacturer
{
string ID { get; set; }
}
Expression<Func<Car, string>> groupKeySelector = x => x.Manufacturer.ID;
Expression<Func<ObjectWithRank<Car>, Car>> rankedObjectSelector = x => x.RankedObject;
var rankedGroupKeySelector = UpdateExpressionRoot(rankedObjectSelector, groupKeySelector);
//rankedGroupKeySelector.ToString() == "x.RankedObject.Manufacturer.ID"
//Essentially this replaced ParameterExpression {x} in x.Manufacturer.ID with MemberExpression {x.RankedObject}.