1

私は、主に EF をよりよく理解するためだけに、小規模な POC に取り組んでいます。以下を実装するためのより効率的な方法はありますか?

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 ステートメントは、WHEN-THEN-THEN ステートメントのセットを生成する代わりに 0 を返します。2 番目の THEN は実質的に最初のステートメントの複製であり、単に否定されていますか? userPerms. Any(...) 呼び出しはブール値を返しますが、WHEN-THEN-ELSE の方が効率的な実装ではないでしょうか? false の場合、(事実上) 同じステートメントが 2 回実行されていませんか?

繰り返しますが、私はこれに慣れていないので、別の方法でモデル化する必要があるか、クエリを別の方法で記述する必要があるかもしれません。舞台裏で何が起こっているのかをよりよく理解したいだけです。

オーバーライドされた 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 を取得する方法はありますか? 私が書いたクエリは 1 つのフィールドだけを返しますが、間違っていると思います。同じクエリを 2 回実行します。

このようなものをもっと生成できるかどうか興味があります。これは、ユーザーがアクセス許可を持っているかどうかを示すビット フィールドのみを返し、多数の 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
4

1 に答える 1

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; }
}

マッピング コードも必要ありません。慣習はあなたにとって正しいことをするはずです。

于 2013-02-27T20:55:49.190 に答える