如何重建一个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

  1. (T x) => x.GroupingProperty
  2. (ObjectWithRank<T> x) => x.RankedObject.GroupingProperty

请注意,我不能随便更改根对象类型keySelectorTObjectWithRank<T>,因为ObjectWithRank<T>类没有公开调用012的公开方法。 API的用户只提供了一个IQueryable<T>,并收到了每个组中排名最高的项目的IQueryable<T>,所以他们从未看到ObjectWithRank<T>在底层使用。

我设法用下面的代码执行转换,但它只适用于简单的成员访问表达式。例如,它可以将如x => x.GroupingKey这样的表达式转换为x => x.RankedObject.GroupingKey,但它不适用于两级深度成员访问表达式,因此我不得不将其转换为x => x.SubObject.GroupingKeyx => 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}.