EF Core:表达式树 - GroupJoin 动态转换为 SelectMany

EF Core : expression trees - GroupJoin transform to SelectMany dynamically

我想转换以下查询:

(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);

像这样:


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

    ....

注:

  1. 我已准备好使用 QueryTranslationPreprocessor

    拦截表达式树求值的设置
  2. 我需要逻辑来生成 .Call.SelectMany 等的输出,方法是使用第一个 linq,然后将其转换为上面显示的第二个 linq 查询

  3. 需要注意的是,表达式引擎在内部生成了如此多的匿名类型,以至于我无法编写一个通用代码来满足具有此类 groupjoins 的不同 linq 情况

这样做的原因:

  1. EF Core 6 中有几种 GroupJoin 社区尚未准备好解决的问题

  2. 我无法直接对 Linq 查询进行更改,因为它们有 1000 个并且在几个地方

因此,在评估多个选项之后,执行上述操作的最佳方法如下所示,位于 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);
    }