表达式树 - 克隆和更改方法调用、Lambda 的类型(参数、Return 类型、匿名类型等)

Expression Tree - Clone and Change Types of MethodCalls, Lambdas (Parameters, Return Types, Anonymous Types etc)

下面的 Linq 查询接收 entitySet 作为 T 类型(属于接口类型)。由于此接口类型,生成的表达式树无法转换为 Sql,因此在表达式树查询评估期间,我需要一种方法来转换所有 Lambda、MethodCalls(连同它们的参数、返回类型(可能是 AnonymousType_of_Interface) 到提供的具体类型。

 IQueryable<T> GetMasterConfigsForMasterConfigExport<T>(IQueryable<T> entitySet, GetMasterConfigsForMasterConfigExportParams p)
        {
            var selectedMasterConfigIds = p.SelectedMasterConfigIds.IsNotBlank() ? p.SelectedMasterConfigIds.ConvertCSVToLong() : Enumerable.Empty<long>();
          

            var query = from masterConfig in entitySet
                        join selectedMasterConfigId in (Repository.ConvertToBigIntTable(selectedMasterConfigIds, "selectedMasterConfigId") as IQueryable<ConvertCSVToBigIntTableResult>)
                        on masterConfig.Id equals selectedMasterConfigId.Id into selectedMasterConfigIdsRS
                        from selectedMasterConfigId in selectedMasterConfigIdsRS.DefaultIfEmpty()
                        where masterConfig.IsActive && selectedMasterConfigId == null
                        select masterConfig;
            return query;
        }

生成的表达式树看起来像这样,我想以某种方式将所有出现的 IMasterConfig 替换为其他提供的具体对象:

.Call System.Linq.Queryable.Count(.Call System.Linq.Queryable.Select(
        .Call System.Linq.Queryable.Where(
            .Call System.Linq.Queryable.SelectMany(
                .Extension<Microsoft.EntityFrameworkCore.Query.QueryRootExpression>,
                '(.Lambda #Lambda1<System.Func`2[IMasterConfig,System.Collections.Generic.IEnumerable`1[Lw.Sys.Repository.ParsedBigInt]]>),
                '(.Lambda #Lambda2<System.Func`3[IMasterConfig,Lw.Sys.Repository.ParsedBigInt,<>f__AnonymousType325`2[IMasterConfig,Lw.Sys.Repository.ParsedBigInt]]>))
            ,
            '(.Lambda #Lambda3<System.Func`2[<>f__AnonymousType325`2[IMasterConfig,Lw.Sys.Repository.ParsedBigInt],System.Boolean]>))
        ,
        '(.Lambda #Lambda4<System.Func`2[<>f__AnonymousType325`2[IMasterConfig,Lw.Sys.Repository.ParsedBigInt],IMasterConfig]>))
)

.Lambda #Lambda1<System.Func`2[IMasterConfig,System.Collections.Generic.IEnumerable`1[Lw.Sys.Repository.ParsedBigInt]]>(IMasterConfig $masterConfig)
{
    .Call System.Linq.Queryable.DefaultIfEmpty(.Call System.Linq.Queryable.Where(
            .Call $__p_0.ConvertCSVToBigIntTable(
                $__List_1,
                $__Delim_2),
            '(.Lambda #Lambda5<System.Func`2[Lw.Sys.Repository.ParsedBigInt,System.Boolean]>)))
}

.Lambda #Lambda2<System.Func`3[IMasterConfig,Lw.Sys.Repository.ParsedBigInt,<>f__AnonymousType325`2[IMasterConfig,Lw.Sys.Repository.ParsedBigInt]]>(
    IMasterConfig $masterConfig,
    Lw.Sys.Repository.ParsedBigInt $selectedMasterConfigId) {
    .New <>f__AnonymousType325`2[IMasterConfig,Lw.Sys.Repository.ParsedBigInt](
        $masterConfig,
        $selectedMasterConfigId)
}

.Lambda #Lambda3<System.Func`2[<>f__AnonymousType325`2[IMasterConfig,Lw.Sys.Repository.ParsedBigInt],System.Boolean]>(<>f__AnonymousType325`2[IMasterConfig,Lw.Sys.Repository.ParsedBigInt] $<>h__TransparentIdentifier0)
{
    ($<>h__TransparentIdentifier0.masterConfig).IsActive && $<>h__TransparentIdentifier0.selectedMasterConfigId == null
}

.Lambda #Lambda4<System.Func`2[<>f__AnonymousType325`2[IMasterConfig,Lw.Sys.Repository.ParsedBigInt],IMasterConfig]>(<>f__AnonymousType325`2[IMasterConfig,Lw.Sys.Repository.ParsedBigInt] $<>h__TransparentIdentifier0)
{
    $<>h__TransparentIdentifier0.masterConfig
}

.Lambda #Lambda5<System.Func`2[Lw.Sys.Repository.ParsedBigInt,System.Boolean]>(Lw.Sys.Repository.ParsedBigInt $selectedMasterConfigId)
{
    $masterConfig.Id == $selectedMasterConfigId.Id
}

正如 @IvonStoev 引用的那样,使用以下代码解决了 EFCore6 中的问题:

 protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            if (node.Method.DeclaringType == typeof(Queryable) && node.Method.Name == nameof(Queryable.GroupJoin) && node.Arguments.Count == 5)
            {
                var outer = Visit(node.Arguments[0]);
                var inner = Visit(node.Arguments[1]);
                var outerKeySelector = Visit(node.Arguments[2]).UnwrapLambdaFromQuote();
                var innerKeySelector = Visit(node.Arguments[3]).UnwrapLambdaFromQuote();
                var resultSelector = Visit(node.Arguments[4]).UnwrapLambdaFromQuote();

                var outerKey = outerKeySelector.Body.ReplaceParameter(outerKeySelector.Parameters[0], resultSelector.Parameters[0]);
                var innerKey = innerKeySelector.Body;
                var keyMatch = MatchKeys(outerKey, innerKey);

                var innerQuery = Expression.Call(
                    typeof(Queryable), nameof(Queryable.Where), new[] { innerKeySelector.Parameters[0].Type },
                    inner, Expression.Lambda(keyMatch, innerKeySelector.Parameters));

                var asEnumerableInnerQuery = Expression.Call(
                                typeof(Enumerable),
                                nameof(Enumerable.AsEnumerable),
                                new Type[] { innerKeySelector.Parameters[0].Type }, innerQuery);

                var resultTypes = resultSelector.Parameters.Select(p => p.Type).ToArray();
                var tempProjectionType = typeof(Tuple<,>).MakeGenericType(resultTypes);
                var tempProjection = Expression.New(
                    tempProjectionType.GetConstructor(resultTypes),
                    new Expression[] { resultSelector.Parameters[0], asEnumerableInnerQuery },
                    tempProjectionType.GetProperty("Item1"), tempProjectionType.GetProperty("Item2"));

                var tempQuery = Expression.Call(
                    typeof(Queryable), nameof(Queryable.Select), new[] { outerKeySelector.Parameters[0].Type, tempProjectionType },
                    outer, Expression.Lambda(tempProjection, resultSelector.Parameters[0]));

                var tempResult = Expression.Parameter(tempProjectionType, "p");
                var projection = resultSelector.Body
                    .ReplaceParameter(resultSelector.Parameters[0], Expression.Property(tempResult, "Item1"))
                    .ReplaceParameter(resultSelector.Parameters[1], Expression.Property(tempResult, "Item2"));

                var query = Expression.Call(
                    typeof(Queryable), nameof(Queryable.Select), new[] { tempProjectionType, projection.Type },
                    tempQuery, Expression.Lambda(projection, tempResult));
                return query;
            }
            return base.VisitMethodCall(node);
        }