当 GroupJoin 作为第一个连接应用于 DbSet 而不是作为第二个或更高版本应用时抛出 ArgumentException

ArgumentException thrown When GroupJoin is applied on a DbSet as a first join and NOT when applied as a second or later

1.例外情况: LINQ:在这里,我有一个查询,它在 DbSet 上执行 GroupJoin,然后添加其他内部联接。 Repository.ConvertToBigIntTable -> 这是对 Table 值函数的调用的输出,该函数 returns 一个 IQueryable(T 是一个具有长 Id 字段)

private IQueryable<IDocumentTypePermission> GetDocumentTypePermissionsTest<T>(IQueryable<T> entitySet) where T : class, IDocumentType
{
    var userGroupIds = RunComponent(GetActiveUserGroupsOfCurrentUser.New(new GetActiveUserGroupsOfCurrentUserParam() { CurrentUserId = CurrentUserId })).UserGroupIds;
    return (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);
}
DbSet<EDocumentType>()
    .GroupJoin(
        inner: BananaContext.ConvertCSVToBigIntTable(
            List: <>c__DisplayClass958_0.List, 
            Delim: <>c__DisplayClass958_0.Delim), 
        outerKeySelector: documentType => documentType.Id, 
        innerKeySelector: userGroupId => userGroupId.Id, 
        resultSelector: (documentType, UserGroupIds) => new { 
            documentType = documentType, 
            UserGroupIds = UserGroupIds
         })
    .SelectMany(
        collectionSelector: <>h__TransparentIdentifier0 => <>h__TransparentIdentifier0.UserGroupIds
            .DefaultIfEmpty(), 
        resultSelector: (<>h__TransparentIdentifier0, userGroupId) => new { 
            <>h__TransparentIdentifier0 = <>h__TransparentIdentifier0, 
            userGroupId = userGroupId
         })
    .Join(
        inner: DbSet<EDocumentTypePermission>(), 
        outerKeySelector: <>h__TransparentIdentifier1 => (long?)<>h__TransparentIdentifier1.<>h__TransparentIdentifier0.documentType.Id, 
        innerKeySelector: documentTypePermission => documentTypePermission.DocumentTypeId, 
        resultSelector: (<>h__TransparentIdentifier1, documentTypePermission) => new { 
            <>h__TransparentIdentifier1 = <>h__TransparentIdentifier1, 
            documentTypePermission = documentTypePermission
         })
    .Join(
        inner: DbSet<EUserSelectionParam>(), 
        outerKeySelector: <>h__TransparentIdentifier2 => <>h__TransparentIdentifier2.documentTypePermission.UserSelectionId, 
        innerKeySelector: userSelectionParam => (long?)userSelectionParam.Id, 
        resultSelector: (<>h__TransparentIdentifier2, userSelectionParam) => new { 
            <>h__TransparentIdentifier2 = <>h__TransparentIdentifier2, 
            userSelectionParam = userSelectionParam
         })
    .Where(<>h__TransparentIdentifier3 => <>h__TransparentIdentifier3.<>h__TransparentIdentifier2.documentTypePermission.IsActive && <>h__TransparentIdentifier3.userSelectionParam.UserGroupId != null && (long?)<>h__TransparentIdentifier3.<>h__TransparentIdentifier2.<>h__TransparentIdentifier1.userGroupId.Id != null || <>h__TransparentIdentifier3.userSelectionParam.UserId != null && <>h__TransparentIdentifier3.userSelectionParam.UserId == (long?)AbstractDomainBehavior.CurrentUserId)
    .Select(<>h__TransparentIdentifier3 => <>h__TransparentIdentifier3.<>h__TransparentIdentifier2.documentTypePermission)

异常:

Expression of type 'System.Func`2[Lw.Domain.IDocumentType,System.Int64]' cannot be used for parameter of type 'System.Linq.Expressions.Expression`1[System.Func`2[Lw.Domain.EDocumentType,System.Int64]]' of method 'System.Linq.IQueryable`1[<>f__AnonymousType290`2[<>f__AnonymousType289`2[Lw.Domain.IDocumentType,System.Collections.Generic.IEnumerable`1[Lw.Sys.Repository.ITableValueFunctionResult]],Lw.Sys.Repository.ITableValueFunctionResult]] LeftJoin[EDocumentType,ITableValueFunctionResult,Int64,<>f__AnonymousType290`2](System.Linq.IQueryable`1[Lw.Domain.EDocumentType], System.Collections.Generic.IEnumerable`1[Lw.Sys.Repository.ITableValueFunctionResult], System.Linq.Expressions.Expression`1[System.Func`2[Lw.Domain.EDocumentType,System.Int64]], System.Linq.Expressions.Expression`1[System.Func`2[Lw.Sys.Repository.ITableValueFunctionResult,System.Int64]], System.Linq.Expressions.Expression`1[System.Func`3[Lw.Domain.EDocumentType,Lw.Sys.Repository.ITableValueFunctionResult,<>f__AnonymousType290`2[<>f__AnonymousType289`2[Lw.Domain.IDocumentType,System.Collections.Generic.IEnumerable`1[Lw.Sys.Repository.ITableValueFunctionResult]],Lw.Sys.Repository.ITableValueFunctionResult]]])' (Parameter 'arg2')

堆栈跟踪:

   at System.Dynamic.Utils.ExpressionUtils.ValidateOneArgument(MethodBase method, ExpressionType nodeKind, Expression arguments, ParameterInfo pi, String methodParamName, String argumentParamName, Int32 index)
   at System.Linq.Expressions.Expression.Call(MethodInfo method, Expression arg0, Expression arg1, Expression arg2, Expression arg3, Expression arg4)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryableMethodNormalizingExpressionVisitor.TryFlattenGroupJoinSelectMany(MethodCallExpression methodCallExpression)
   at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
   at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryableMethodNormalizingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
   at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryableMethodNormalizingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
   at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryableMethodNormalizingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
   at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryableMethodNormalizingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at Microsoft.EntityFrameworkCore.Query.QueryTranslationPreprocessor.NormalizeQueryableMethod(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryTranslationPreprocessor.NormalizeQueryableMethod(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.QueryTranslationPreprocessor.Process(Expression query)
   at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass9_0`1.<Execute>b__0()
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetEnumerator()
   at System.Collections.Generic.LargeArrayBuilder`1.AddRange(IEnumerable`1 items)
   at System.Collections.Generic.EnumerableHelpers.ToArray[T](IEnumerable`1 source)
   at System.Linq.SystemCore_EnumerableDebugView`1.get_Items()

注意:有趣的是,尝试在 LinqPad7 上执行非常相似的操作似乎可行:

2。工作案例 LINQ:在这里,我有一个查询,它在几个 DbSet 上执行内部联接,然后在其中一个 DbSet 上应用 GroupJoin。 Repository.ConvertToBigIntTable -> 这是对 Table 值函数的调用的输出,该函数 returns 一个 IQueryable(T 是一个具有长 Id 字段)

private IQueryable<IDocumentTypePermission> GetDocumentTypePermissions<T>(IQueryable<T> entitySet) where T : class, IDocumentType
{
    var userGroupIds = RunComponent(GetActiveUserGroupsOfCurrentUser.New(new GetActiveUserGroupsOfCurrentUserParam() { CurrentUserId = CurrentUserId })).UserGroupIds;
    return (from documentType in entitySet
            join documentTypePermission in Repository.DocumentTypePermissions on documentType.Id equals documentTypePermission.DocumentTypeId
            join userSelectionParam in Repository.UserSelectionParams on documentTypePermission.UserSelectionId equals userSelectionParam.Id
            join userGroupId in Repository.ConvertToBigIntTable(userGroupIds, "userGroupId")
            on userSelectionParam.UserGroupId equals userGroupId.Id into UserGroupIds
            from userGroupId in UserGroupIds.DefaultIfEmpty()
            where documentTypePermission.IsActive && ((userSelectionParam.UserGroupId != null && userGroupId.Id != null)
                                                        || (userSelectionParam.UserId != null && userSelectionParam.UserId == CurrentUserId))
            select documentTypePermission);
}

生成的表达式:

DbSet<EDocumentType>()
    .Join(
        inner: DbSet<EDocumentTypePermission>(), 
        outerKeySelector: documentType => (long?)documentType.Id, 
        innerKeySelector: documentTypePermission => documentTypePermission.DocumentTypeId, 
        resultSelector: (documentType, documentTypePermission) => new { 
            documentType = documentType, 
            documentTypePermission = documentTypePermission
         })
    .Join(
        inner: DbSet<EUserSelectionParam>(), 
        outerKeySelector: <>h__TransparentIdentifier0 => <>h__TransparentIdentifier0.documentTypePermission.UserSelectionId, 
        innerKeySelector: userSelectionParam => (long?)userSelectionParam.Id, 
        resultSelector: (<>h__TransparentIdentifier0, userSelectionParam) => new { 
            <>h__TransparentIdentifier0 = <>h__TransparentIdentifier0, 
            userSelectionParam = userSelectionParam
         })
    .GroupJoin(
        inner: BananaContext.ConvertCSVToBigIntTable(
            List: <>c__DisplayClass958_0.List, 
            Delim: <>c__DisplayClass958_0.Delim), 
        outerKeySelector: <>h__TransparentIdentifier1 => <>h__TransparentIdentifier1.userSelectionParam.UserGroupId, 
        innerKeySelector: userGroupId => (long?)userGroupId.Id, 
        resultSelector: (<>h__TransparentIdentifier1, UserGroupIds) => new { 
            <>h__TransparentIdentifier1 = <>h__TransparentIdentifier1, 
            UserGroupIds = UserGroupIds
         })
    .SelectMany(
        collectionSelector: <>h__TransparentIdentifier2 => <>h__TransparentIdentifier2.UserGroupIds
            .DefaultIfEmpty(), 
        resultSelector: (<>h__TransparentIdentifier2, userGroupId) => new { 
            <>h__TransparentIdentifier2 = <>h__TransparentIdentifier2, 
            userGroupId = userGroupId
         })
    .Where(<>h__TransparentIdentifier3 => <>h__TransparentIdentifier3.<>h__TransparentIdentifier2.<>h__TransparentIdentifier1.<>h__TransparentIdentifier0.documentTypePermission.IsActive && <>h__TransparentIdentifier3.<>h__TransparentIdentifier2.<>h__TransparentIdentifier1.userSelectionParam.UserGroupId != null && (long?)<>h__TransparentIdentifier3.userGroupId.Id != null || <>h__TransparentIdentifier3.<>h__TransparentIdentifier2.<>h__TransparentIdentifier1.userSelectionParam.UserId != null && <>h__TransparentIdentifier3.<>h__TransparentIdentifier2.<>h__TransparentIdentifier1.userSelectionParam.UserId == (long?)AbstractDomainBehavior.CurrentUserId)
    .Select(<>h__TransparentIdentifier3 => <>h__TransparentIdentifier3.<>h__TransparentIdentifier2.<>h__TransparentIdentifier1.<>h__TransparentIdentifier0.documentTypePermission)

生成SQL:

DECLARE @__List_1 nvarchar(4000) = N'1,5,6,7,8,14,15,44,46,62,63,64,67,69,73,85,88';
DECLARE @__Delim_2 nvarchar(1) = N',';
DECLARE @__CurrentUserId_3 bigint = CAST(1 AS bigint);

SELECT [d0].[Id], [d0].[Condition], [d0].[CreatedById], [d0].[CreatedTime], [d0].[DocumentTypeId], [d0].[IsActive], [d0].[IsOverridable], [d0].[IsReevaluate], [d0].[RowVersion], [d0].[UpdatedById], [d0].[UpdatedTime], [d0].[UserSelectionId], [d0].[AssignmentType], [d0].[ConditionFor], [d0].[CreationAllowed], [d0].[Permission]
FROM [DocumentTypes] AS [d]
INNER JOIN [DocumentTypePermissions] AS [d0] ON [d].[Id] = [d0].[DocumentTypeId]
INNER JOIN [UserSelectionParams] AS [u] ON [d0].[UserSelectionId] = [u].[Id]
LEFT JOIN [dbo].[ConvertCSVToBigIntTable](@__List_1, @__Delim_2) AS [c] ON [u].[UserGroupId] = [c].[Id]
WHERE ([d0].[IsActive] = CAST(1 AS bit)) AND (([u].[UserGroupId] IS NOT NULL AND [c].[Id] IS NOT NULL) OR ([u].[UserId] IS NOT NULL AND ([u].[UserId] = @__CurrentUserId_3)))
EF Core version: 6.0.1
Database provider:  Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET 6.0
Operating system: Win 10 Pro
IDE: Visual Studio 2022 v17.0.4

更新:一个更特殊的情况:如果我然后使用直接 DbContext.DbSet 引用而不是使用通用对象“entitySet”,那么所有错误都会消失并且 Sql 得到正确生成:

 private IQueryable<IDocumentTypePermission> GetDocumentTypePermissionsTest<T>(IQueryable<T> entitySet) where T : class, IDocumentType
        {
            var userGroupIds = RunComponent(GetActiveUserGroupsOfCurrentUser.New(new GetActiveUserGroupsOfCurrentUserParam() { CurrentUserId = CurrentUserId })).UserGroupIds;
            return (from documentType in Repository.GetDbContext().DocumentTypes
                    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);
        }
    ```

尝试将 GroupJoin 替换为 SelectMany

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

    ....