如何重建一个lambda表达式以更深入地启动一个级别?
我创建了一个通用方法,它接受标识分组键的成员访问表达式,就像人们传递给IQueryable<T>.GroupBy
一样。如何重建一个lambda表达式以更深入地启动一个级别?
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>
类没有公开调用012的公开方法。 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
);
}
上面看起来像一个劈其中我首先创建访问ObjectWithRank<T>
的T RankedObject
属性的成员访问的表达,然后钉在所提供的keySelector
成员访问的表达。我不确定是否有一种简单的方法可以使其发挥作用。看起来Expression.Property只允许一次钻取一个属性,所以也许我需要某种循环来重建顶部的表达式,一次钻取一个属性。
这里有一个类似的问题,确实有一个简单的解决方案,但在表达式的另一端更深一层,这不是我想要做的。 Alter Lambda Expression to go one level deeper
我能够与在构件表达向下钻取递归兰巴替换表达式的根,直到它到达参数表达,在该最深层次替换为新的根表达的参数表达,然后退绕调用堆栈将每个成员表达式的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}.