2013-02-27 90 views
1

我正在研究一个小的POC,主要是为了帮助我更好地理解EF。有没有更有效的方法来实现以下内容?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,而不是产生-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 
+1

查询性能不佳吗?你有更高效的SQL实现吗? – Aducci 2013-02-27 20:30:04

+0

对不起......我用SQL Profiler报告了更新了生成的SQL。我以前显示的只是query.ToString()的结果。 我在期待一个ELSE语句,而不是一个非常相似的THEN语句。我会在我的问题上加上这个期望。 – Paul 2013-02-27 20:46:21

+0

好吧,我已经更新了这个问题... – Paul 2013-02-27 20:53:30

回答

1

您应该能够使用:

return user.RolesUsers 
    .SelectMany(ru => ru.Role.RolesPermissions) 
    .Any(up => up.PermissionId == permission.Id); 

由于您的RoleUserRolePermission类是简单许多一对多的容器,我倾向于将其删除,去直许多一对多的关系:

public class Role : Base 
{ 
    public virtual ICollection<Permission> Permissions { get; set; } 
    public virtual ICollection<User> Users { get; set; } 
} 

public class User : Base 
{ 
    public virtual ICollection<Role> Roles { get; set; } 
    public string AuthenticationId { get; set; } 
} 

public class Permission : Base 
{ 
    public virtual ICollection<Role> Roles { get; set; } 
} 

你甚至不需要任何映射代码;公约应该为你做正确的事情。

+0

谢谢理查德。我将研究由修改后的查询生成的SQL。 关于模型,我从你建议的方法开始,但是当我考虑向交叉表添加字段时切换。例如,我有一个UsersPermissions表,它有预期的外键以及一个授权位字段。这将允许删除对个人权限的访问。 我还没有承诺,因为我认为这可能会使事情过于复杂。我可以让这些组更加细化以避免这种情况。长话短说,我可以回到你建议的方法。 – Paul 2013-02-27 21:15:25

+0

我已经更新了我的帖子并回复了你的回答。 – Paul 2013-02-27 21:34:52