在遍历 EF Core 5 中的多个条件包含后查询任何条目

Posted

技术标签:

【中文标题】在遍历 EF Core 5 中的多个条件包含后查询任何条目【英文标题】:Querying for any entries after traversing multiple conditional includes in EF Core 5 【发布时间】:2021-11-27 14:48:15 【问题描述】:

考虑以下数据模型:

一个委托人有多个角色(多对多); 角色授予多个权限(多对多);

现在我想使用 LINQ 来确定 Principal 是否具有权限,即他是否在任何拥有此权限的角色中。 对于这种情况,通常我会在连接表上使用AnyAsync,但由于我要遍历多个连接表,所以我真的很挣扎。我最初想出了这个:

    var query = context.Principals
        .Where(p => p.Id == "SomeUUID")
        .Include(p => p.Roles)
        .ThenInclude(r => r.Permissions
        .Where(p => p.Name == "SomePermission"));

但现在我必须再次通过 Principal 遍历(可能在内存中)附加的角色以搜索 Any 权限。

有没有办法可以在同一个查询中顺利应用此检查?

编辑:这是生成的 SQL 以补充 Parks 的(底部)答案:

SELECT CASE
      WHEN EXISTS (
          SELECT 1
          FROM [Principals] AS [a]
          INNER JOIN (
              SELECT [a1].[Id], [a1].[Description], [a1].[DisplayName], [a0].[RoleId], [a0].[PrincipalId]
              FROM [PrincipalRoles] AS [a0]
              INNER JOIN [Roles] AS [a1] ON [a0].[RoleId] = [a1].[Id]
          ) AS [t] ON [a].[PrincipalId] = [t].[PrincipalId]
          INNER JOIN (
              SELECT [a3].[Id], [a3].[Name], [a2].[RoleId], [a2].[PermissionId]
              FROM [RolePermissions] AS [a2]
              INNER JOIN [Permissions] AS [a3] ON [a2].[PermissionId] = [a3].[Id]
          ) AS [t0] ON [t].[Id] = [t0].[RoleId]
          WHERE ([a].[PrincipalId] = "SomePrincipal") AND ([t0].[Name] = "SomePermission")) THEN CAST(1 AS bit)
      ELSE CAST(0 AS bit)
END

SELECT CASE
      WHEN EXISTS (
          SELECT 1
          FROM [Principals] AS [a]
          WHERE ([a].[PrincipalId] = "SomePrincipal") AND EXISTS (
              SELECT 1
              FROM [PrincipalRoles] AS [a0]
              INNER JOIN [Roles] AS [a1] ON [a0].[RoleId] = [a1].[Id]
              WHERE ([a].[PrincipalId] = [a0].[PrincipalId]) AND EXISTS (
                  SELECT 1
                  FROM [RolePermissions] AS [a2]
                  INNER JOIN [Permissions] AS [a3] ON [a2].[PermissionId] = [a3].[Id]
                  WHERE ([a1].[Id] = [a2].[RoleId]) AND ([a3].[Name] = "SomePermission")))) THEN CAST(1 AS bit)
      ELSE CAST(0 AS bit)
END

【问题讨论】:

【参考方案1】:

要查看主体是否在 Roles 中具有在 Permissions 中具有 Permission 的 Role,您可以使用以下 Where 条件:

var result = await context.Principals
    .Where(p => p.Id == "SomeUUID" && p.Roles.Any(r => r.Permissions.Any(x => x.Name == "SomePermission")))
    .FirstOrDefaultAsync();

如果 result 为 null,则 Principal 不存在或没有您检查的权限。如果result 仍然需要导航属性,那么您可以添加最初包含在您的问题中的包含语句。

【讨论】:

谢谢,我只是去了AnyAsync,因为我根本不需要检索结果。我最初的查询正是我期望的结果看起来相似。【参考方案2】:

您可能正在寻找此查询:

var hasPermssion = context.Principals
    .Where(p => p.Id == "SomeUUID")
    .SelectMany(p => p.Roles)
    .SelectMany(r => r.Permissions)
    .Any(p => p.Name == "SomePermission");

生成的SQL语句如下:

SELECT CASE
      WHEN EXISTS (
          SELECT 1
          FROM [Principals] AS [a]
          INNER JOIN (
              SELECT [a1].[Id], [a1].[Description], [a1].[DisplayName], [a0].[RoleId], [a0].[PrincipalId]
              FROM [PrincipalRoles] AS [a0]
              INNER JOIN [Roles] AS [a1] ON [a0].[RoleId] = [a1].[Id]
          ) AS [t] ON [a].[PrincipalId] = [t].[PrincipalId]
          INNER JOIN (
              SELECT [a3].[Id], [a3].[Name], [a2].[RoleId], [a2].[PermissionId]
              FROM [RolePermissions] AS [a2]
              INNER JOIN [Permissions] AS [a3] ON [a2].[PermissionId] = [a3].[Id]
          ) AS [t0] ON [t].[Id] = [t0].[RoleId]
          WHERE ([a].[PrincipalId] = "SomePrincipal") AND ([t0].[Name] = "SomePermission")) THEN CAST(1 AS bit)
      ELSE CAST(0 AS bit)
END

【讨论】:

我认为您可以在此查询中使用 Where 而不是 Any。 不确定,我们必须检查 Principal 是否有权限,并且检索记录是多余的。 是的,但它只是检索匹配的权限。要么应该工作。无论如何,我认为 SelectMany 或它的 LINQ 等价于 stacking FROM 是更简单的模式。 Any 更改为FirstOrDefault 很容易。在这里受益,SQL Server 会很高兴:只有连接和一个 EXISTS。 是的,AnyAsync 就足够了。我确实相信这是更简洁的解决方案,但我注意到它的性能比@Jeffrey Parks 解决方案略差,至少在可用数据很少的情况下。这可能是由于查询角色/权限时内部连接的SELECT 包括所有列吗?有没有一种方法可以在这里应用投影来从SelectMany 中挤出更多的性能?

以上是关于在遍历 EF Core 5 中的多个条件包含后查询任何条目的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 EF Core 3.0 使多个包含更高效

如何在 C# EF Core 中使用 join 子句中的条件编写 SQL 命令

EF Core 中实现 动态数据过滤器

比较 EF Core Linq 查询中的 DateTime

如何在 EF / EF Core 中的第二个表上实现具有某些条件的左连接?

如何编写转换为 T-SQL 的 EF Core 查询包含