2013-02-27 5 views
1

Я работаю над небольшим POC главным образом, чтобы помочь мне лучше понять EF. Есть ли более эффективный способ реализовать следующее?EF Query Optimization

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 генерировать КОГДА-ТО-ELSE заявления (где оператор ELSE возвращает 0 вместо генерирования КОГДА-ТО-ТО установить из утверждений, где второй 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

Развейте на вопросы ниже:

@ [Richard Deeming]

Ниже приводится результат ваших предлагаемых изменений в запросе.

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' 

Хотя, я вижу, КОГДА-ТО-ИНАЧЕ, порожденный этим запросом, она возвращает дополнительные столбцы, которые не требуются. Есть ли способ заставить 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

+0

Извините ... Я обновил сгенерированный SQL с сообщением SQL Profiler. То, что я отобразил ранее, было результатом запроса. ToString(). Я ожидал утверждения ELSE вместо очень похожего утверждения THEN. Я добавлю это ожидание на мой вопрос. – Paul

+0

Хорошо, я обновил вопрос ... – Paul

ответ

1

Вы должны быть в состоянии использовать:

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

Поскольку ваши RoleUser и RolePermission классов просто многие-ко-многим контейнеров, я был бы склонен удалить их и идти на прямой многие-ко-многим:

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, в которой у нее были ожидаемые внешние ключи, а также поле бит Granted. Это позволит удалить доступ к отдельным разрешениям. Я этого не делал, потому что я думаю, что это может слишком усложнить ситуацию. Я мог бы просто сделать группы более гранулированными, чтобы этого избежать. Короче говоря, я могу вернуться к вашему предлагаемому подходу. – Paul

+0

Я обновил сообщение с ответом на ваш ответ. – Paul