Search code examples
c#entity-framework-coreexpression-treesef-core-6.0

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


The below Linq query receives entitySet as an T type (which is of an Interface type). The generated expression tree fails to transform to Sql due to this Interface type, and hence during the expression tree query evaluation I need a way to transform all Lambdas, MethodCalls (along with their parameters, returntypes (which could be AnonymousType_of_Interface) to a supplied concrete type.

 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;
        }

The Generated Expression tree looks like this, I would like to replace all occurrences of IMasterConfig to some other supplied concrete object somehow:

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


Solution

  • As cited by @IvonStoev, using the following code solves the issue in 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);
            }