Home > Enterprise >  EF Core : expression trees - GroupJoin transform to SelectMany dynamically
EF Core : expression trees - GroupJoin transform to SelectMany dynamically

Time:02-12

I would like to transform the below query:

(from documentType in entitySet
 join userGroupId in Repository.ConvertToBigIntTable(userGroupIds, "userGroupId")
      on documentType.Id equals userGroupId.Id into UserGroupIds
 from userGroupId in UserGroupIds.DefaultIfEmpty()
 join documentTypePermission in Repository.DocumentTypePermissions
      on documentType.Id equals documentTypePermission.DocumentTypeId
 join userSelectionParam in Repository.UserSelectionParams
      on documentTypePermission.UserSelectionId equals userSelectionParam.Id
 where documentTypePermission.IsActive && 
       ((userSelectionParam.UserGroupId != null && userGroupId.Id != null)
        || (userSelectionParam.UserId != null && userSelectionParam.UserId == CurrentUserId))
 select documentTypePermission);

to something like this:


var query = 
    from documentType in entitySet
    from userGroupId in Repository.ConvertToBigIntTable(userGroupIds, "userGroupId")
        .Where(userGroupId => documentType.Id == userGroupId.Id)
        .DefaultIfEmpty()
    join documentTypePermission in Repository.DocumentTypePermissions
        on documentType.Id equals documentTypePermission.DocumentTypeId
    join userSelectionParam in Repository.UserSelectionParams
        on documentTypePermission.UserSelectionId equals userSelectionParam.Id

    ....

Note:

  1. I have the setup ready for intercepting the expression tree evaluation using QueryTranslationPreprocessor

  2. I need the logic to generate the output of .Call, .SelectMany etc by using the first linq and then translating the same to second linq query shown above

  3. The caveats are that internally the expression engine generates so many Anonymous types that I am unable to write a generic code that would suffice different cases of linq with such groupjoins

Reason to do this:

  1. There are several kinds of GroupJoin issues in EF Core 6 which the community isn’t ready to resolve

  2. I can't make the change to Linq query directly as there are 1000s of them and in several places

CodePudding user response:

So after evaluating multiple options, the best way to do the above is as below at 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);
    }
  • Related