我正在研究一个小型 POC,主要是为了帮助我更好地理解 EF。有没有更有效的方法来实现以下?
private static bool IsUserGrantedPermission(DatabaseContext db, Permission permission, User user)
{
var userRoles = db.Roles.Where(r => r.RolesUsers.Any(ru => ru.UserId == user.Id));
var userPerms = db.Permissions.Where(p => p.RolesPermissions.Any(rp => userRoles.Any(ur => ur.Id == rp.RoleId)));
//Console.WriteLine(userPerms.ToString());
return userPerms.Any(up => up.Id == permission.Id);
}
这是生成的 SQL:
exec sp_executesql N'SELECT
CASE WHEN ( EXISTS (SELECT
1 AS [C1]
FROM [dbo].[Permissions] AS [Extent1]
WHERE ( EXISTS (SELECT
1 AS [C1]
FROM ( SELECT
[Extent2].[RoleId] AS [RoleId]
FROM [dbo].[RolesPermissions] AS [Extent2]
WHERE [Extent1].[Id] = [Extent2].[PermissionId]
) AS [Project1]
WHERE EXISTS (SELECT
1 AS [C1]
FROM [dbo].[Roles] AS [Extent3]
WHERE ( EXISTS (SELECT
1 AS [C1]
FROM [dbo].[RolesUsers] AS [Extent4]
WHERE ([Extent3].[Id] = [Extent4].[RoleId]) AND ([Extent4].[UserId] = @p__linq__0)
)) AND ([Extent3].[Id] = [Project1].[RoleId])
)
)) AND ([Extent1].[Id] = @p__linq__1)
)) THEN cast(1 as bit) WHEN ( NOT EXISTS (SELECT
1 AS [C1]
FROM [dbo].[Permissions] AS [Extent5]
WHERE ( EXISTS (SELECT
1 AS [C1]
FROM ( SELECT
[Extent6].[RoleId] AS [RoleId]
FROM [dbo].[RolesPermissions] AS [Extent6]
WHERE [Extent5].[Id] = [Extent6].[PermissionId]
) AS [Project6]
WHERE EXISTS (SELECT
1 AS [C1]
FROM [dbo].[Roles] AS [Extent7]
WHERE ( EXISTS (SELECT
1 AS [C1]
FROM [dbo].[RolesUsers] AS [Extent8]
WHERE ([Extent7].[Id] = [Extent8].[RoleId]) AND ([Extent8].[UserId] = @p__linq__0)
)) AND ([Extent7].[Id] = [Project6].[RoleId])
)
)) AND ([Extent5].[Id] = @p__linq__1)
)) THEN cast(0 as bit) END AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable1]',N'@p__linq__0 uniqueidentifier,@p__linq__1 uniqueidentifier',@p__linq__0='C0E7EB21-BB3D-424E-8EF0-48A6C9526410',@p__linq__1='A94F0203-B97B-46FF-824D-BBA9D482E674'
为什么 EF 不生成 WHEN-THEN-ELSE 语句(其中 ELSE 语句返回 0 而不是生成 WHEN-THEN-THEN 语句集,其中第二个 THEN 实际上是第一个的副本,只是被否定了?因为 userPerms. Any(...) 调用返回布尔值,WHEN-THEN-ELSE 不是更有效的实现吗?在错误的情况下,不是(实际上)相同的语句运行两次吗?
再说一次,我是新手,所以也许我只需要以不同的方式建模,或者我需要以不同的方式编写查询。我只是想更好地了解幕后发生的事情。
这是重写的 OnModelCreating 函数。
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<RoleUser>()
.HasKey(ru => new { ru.RoleId, ru.UserId })
.ToTable("RolesUsers");
modelBuilder.Entity<User>()
.HasMany(u => u.RolesUsers)
.WithRequired()
.HasForeignKey(ru => ru.UserId);
modelBuilder.Entity<Role>()
.HasMany(r => r.RolesUsers)
.WithRequired()
.HasForeignKey(ru => ru.RoleId);
modelBuilder.Entity<RolePermission>()
.HasKey(rp => new { rp.RoleId, rp.PermissionId })
.ToTable("RolesPermissions");
modelBuilder.Entity<Permission>()
.HasMany(p => p.RolesPermissions)
.WithRequired()
.HasForeignKey(rp => rp.PermissionId);
modelBuilder.Entity<Role>()
.HasMany(r => r.RolesPermissions)
.WithRequired()
.HasForeignKey(rp => rp.RoleId);
modelBuilder.Entity<User>()
.HasKey(user => user.Id)
.Property(user => user.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<User>()
.Property(user => user.Name);
modelBuilder.Entity<Role>()
.HasKey(role => role.Id)
.Property(role => role.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<Role>()
.Property(role => role.Name);
modelBuilder.Entity<Permission>()
.HasKey(permission => permission.Id)
.Property(permission => permission.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<Permission>()
.Property(permission => permission.Name);
base.OnModelCreating(modelBuilder);
}
如果您觉得更容易,该代码也位于此处:http: //samplesecurityapp.codeplex.com/SourceControl/changeset/view/24664#385932
跟进以下问题:
@[理查德认为]
以下是您对查询的建议更改的结果。
exec sp_executesql N'SELECT
[Project1].[C1] AS [C1],
[Project1].[Id] AS [Id],
[Project1].[AuthenticationId] AS [AuthenticationId],
[Project1].[Name] AS [Name],
[Project1].[C2] AS [C2],
[Project1].[RoleId] AS [RoleId],
[Project1].[UserId] AS [UserId]
FROM ( SELECT
[Limit1].[Id] AS [Id],
[Limit1].[AuthenticationId] AS [AuthenticationId],
[Limit1].[Name] AS [Name],
1 AS [C1],
[Extent2].[RoleId] AS [RoleId],
[Extent2].[UserId] AS [UserId],
CASE WHEN ([Extent2].[RoleId] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C2]
FROM (SELECT TOP (2) [Extent1].[Id] AS [Id], [Extent1].[AuthenticationId] AS [AuthenticationId], [Extent1].[Name] AS [Name]
FROM [dbo].[Users] AS [Extent1]
WHERE [Extent1].[Id] = @p__linq__0 ) AS [Limit1]
LEFT OUTER JOIN [dbo].[RolesUsers] AS [Extent2] ON [Limit1].[Id] = [Extent2].[UserId]
) AS [Project1]
ORDER BY [Project1].[Id] ASC, [Project1].[C2] ASC',N'@p__linq__0 uniqueidentifier',@p__linq__0='C0E7EB21-BB3D-424E-8EF0-48A6C9526410'
虽然,我确实看到了此查询生成的 WHEN-THEN-ELSE,但它返回了不需要的其他列。有没有办法让 EF 只返回一个位字段,指示给定用户是否具有给定权限?我写的查询确实只返回一个字段,但我相信在错误的情况下,它会运行相同的查询两次。
我很好奇我是否可以生成更像这样的东西。它是这两种方法的混合,因为它只返回一个位字段,指示用户是否具有权限,并且它使用连接而不是大量的 WHERE EXISTS 语句
DECLARE @UserId UNIQUEIDENTIFIER
DECLARE @PermissionId UNIQUEIDENTIFIER
SET @UserId = '151b517b-051f-4040-b6c6-036dd06d661d';
SET @PermissionId = '2A379840-F44D-4D09-AAD5-2B34EDF1EDC9';
SELECT
CASE
WHEN (
EXISTS(
SELECT p.Id
FROM Permissions p
INNER JOIN RolesPermissions rp
ON p.Id = rp.PermissionId
INNER JOIN Roles r
ON rp.RoleId = r.id
INNER JOIN RolesUsers ru
ON r.id = ru.RoleId
WHERE ru.UserId = @UserId AND p.Id = @PermissionId
)
) THEN cast(1 AS BIT)
ELSE CAST(0 AS BIT)
END