如何重建lambda表达式以更深层次开始?

时间:2017-07-16 15:39:36

标签: c# lambda linq-to-entities expression

我创建了一个通用方法,它接受一个标识分组键的成员访问表达式,就像传递给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

  1. IQueryable<ObjectWithRank<T>>.GroupBy
  2. (T x) => x.GroupingProperty
  3. 请注意,我不能只将(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

1 个答案:

答案 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}.