首页 文章

.NET Core 2.1标识:为每个角色桥M:M表创建一个表

提问于
浏览
1

我在使用.NET Core 2.1项目中的Identity进行基于角色的授权的最佳设计方面遇到了问题 .

我已经使用ApplicationUser类从Identity扩展了User类 . 我需要5个不同的角色来控制对应用程序不同功能的访问:

管理员,教师,学生,家长和主管

所有常用属性都保存在User和ApplicationUser中,但我仍然需要与其他表的不同关系,具体取决于用户的角色 .

  • 角色教师中的用户与1-N学校相关联

  • 角色中的用户学生与1-N GroupOfStudents相关联(但不直接与学校相关)

  • 角色中的用户父级链接到1-N学生(但不是学校)

  • ......

另一个要求是用户必须能够处于1-N角色 .

在我的案例中,最佳做法是什么?

在身份的特征中是否有一些我缺少的东西?

我的想法最初是使用可空的FK,但随着角色数量的增加,为所有这些记录设置这么多空字段似乎不是一个好主意 .

我正在考虑使用“桥接表”将用户链接到每个角色的其他表 . 在ApplicationUser和桥接表之间 Build 多对多关系,并在桥接表和每个角色的各个表之间 Build 0-1关系 . 但这并不是真的有帮助,因为每个记录都会产生相同数量的空字段 .

我是.NET Core的新手,特别是Identity,我可能错过了一些关键词来进行有效的研究,因为它让我觉得它是一个非常基本的系统(在需求中没什么特别的花哨) .

谢谢阅读 !

EDIT : 在尝试深入项目之前,我没有试图找出最佳实践 . 因为's the first time I face that kind of requirement, I'm试图找到有关利弊的文件 .

我遵循了Marco的想法,并将继承用于我的基于角色的模型,因为这是我的第一个想法 . 我希望这有助于理解我的担忧 .

public class ApplicationUser : IdentityUser
{
    public string CustomTag { get; set; }
    public string CustomTagBis { get; set; }
}
    public class Teacher : ApplicationUser
{
    public string TeacherIdentificationNumber { get; set; }
    public ICollection<Course> Courses { get; set; }
}
public class Student : ApplicationUser
{
    public ICollection<StudentGroup> Groups { get; set; }
}
public class Parent : ApplicationUser
{
    public ICollection<Student> Children { get; set; }
}
public class Course
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Category { get; set; }
}
public class StudentGroup
{
    public int Id { get; set; }
    public string Name { get; set; }
}

这将创建一个数据库,其中包含一个包含所有属性的User的大表:

用户表生成
image

我可以使用它,它会工作 . 如果用户需要担任不同的角色,则可以填写任何可以为空的字段 .

我担心的是,对于每条记录,我都会有大量“不合适的字段”,这些字段仍然是空的 . 假设有1000名用户,80%的用户是学生 . 让800行包含以下内容的后果是: - 一个空的ParentId FK - 一个空的TeacherIdentificationNumber

这只是模型内容的一小部分 . 它没有“感觉”正确,我错了吗?

是不是有更好的方法来设计实体,以便表User只包含所有用户的公共属性(因为它应该?),并且仍然能够将每个用户链接到另一个将User连接到1的表-N表老师/学生/家长/ ...表?

每层次表的方法图
image

EDIT 2: 使用Marco的答案,我尝试使用Table-Per-Type方法 . 在修改我的上下文以实现Table-Per-Type方法时,我想在添加迁移时遇到此错误:

“实体类型'IdentityUserLogin'需要定义主键 . ”

我相信这是因为我删除了:

base.OnModelCreating(builder);

导致拥有此代码:

protected override void OnModelCreating(ModelBuilder builder)
{
        //base.OnModelCreating(builder);
        builder.Entity<Student>().ToTable("Student");
        builder.Entity<Parent>().ToTable("Parent");
        builder.Entity<Teacher>().ToTable("Teacher");
}

我相信那些身份密钥映射在base.OneModelCreating中 . 但即使我取消注释该行,我在数据库中保留相同的结果 .

经过一些研究,我发现this article帮助我完成了创建Table-per-type模型并应用迁移的过程 .

使用这种方法,我有一个如下所示的模式:Table-Per-Type方法
image

如果我错了,请纠正我,但这两种技术都符合我的要求,更多的是关于设计的偏好?它在架构和身份特征方面没有重大影响?


对于第三种选择,我想使用不同的方法,但我不太确定 .

Does a design like this could fit my requirements and is it valid? 有效,我的意思是,将教师实体与角色而不是用户相关联会感觉很奇怪 . 但在某种程度上,教师实体代表用户在教师角色中所需的功能 .

对实体的作用
image

我还不太清楚如何使用EF核实现它,以及如何重写IdentityRole类将影响Identity功能 . 我在上面但尚未弄明白 .

2 回答

  • 1

    我建议你利用asp.net核心的新功能和新的Identity框架 . 有很多关于security的文档 .

    您可以使用policy based安全性,但在您的情况下resource-based安全性似乎更合适 .

    最好的方法是不混合上下文 . 保持关注点:身份上下文(使用UserManager)和业务上下文(学校,您的DbContext) .

    因为将ApplicationUser表放在您的“业务上下文”表示您直接访问Identity上下文 . 这不是你应该使用Identity的方式 . 使用UserManager进行与IdentityUser相关的查询 .

    为了使其工作,而不是继承ApplicationUser表,在学校上下文中创建一个用户表 . 它不是副本而是新表 . 事实上,唯一的共同点是UserId字段 .

    检查我的答案here,了解更详细的设计 .

    将TeacherIdentificationNumber之类的字段移出ApplicationUser . 您可以将此作为声明添加到用户(AspNetUserClaims表):

    new Claim("http://school1.myapp.com/TeacherIdentificationNumber", 123);
    

    或将其存储在学校环境中 .

    另外角色考虑使用声明,您可以通过类型名称区分声明(例如http:// school1 .myapp.com / role):

    new Claim("http://school1.myapp.com/role", "Teacher");
    new Claim("http://school2.myapp.com/role", "Student");
    

    虽然我认为在您的情况下,最好将信息存储在学校环境中 .

    底线,保持身份上下文不变,并将表添加到学校上下文中 . 您不必创建两个数据库,只是不添加跨上下文关系 . 绑定这两者的唯一因素是UserId . 但是,您不需要实际的数据库关系 .

    使用UserManager等进行身份查询以及您的应用程序的学校环境 . 如果不进行身份验证,则不应使用Identity上下文 .


    现在,在设计中,创建一个具有匹配的UserId字段的用户表,以链接当前用户 . 仅当您要显示此信息时(在报告上),才添加名称等字段 .

    为学生,教师等添加一个表,使用复合键:School.Id,User.Id . 或者添加一个公共ID并对School.Id,User.Id的组合使用唯一约束 .

    当用户出现在表格中时,这意味着用户是学校的学生x或学校的老师 . 不需要Identity环境中的角色 .

    使用导航属性,您可以轻松确定“角色”并访问该“角色”的字段 .

  • 2

    你所做的完全符合你的要求 . 您目前实施的内容称为 Table-Per-Hierarchy . 这是Entity Framework在发现其模型时所采用的默认方法 .

    另一种方法是 Table-Per-Type . 在这种情况下,Entity Framework将创建4个表 .

    • 用户表

    • 学生表

    • 教师表

    • 父表

    由于所有这些实体都继承自 ApplicationUser ,因此数据库将在它们与其父类之间生成FK关系 .

    要实现此功能,您需要修改DbContext:

    public class FooContext : DbContext
    {
        public DbSet<ApplicationUser> Users { get; set; }
    
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Student>().ToTable("Students");
            modelBuilder.Entity<Parent>().ToTable("Parents");
            modelBuilder.Entity<Teacher>().ToTable("Teachers");
        }
    }
    

    这应该是最规范化的方法 . 然而,有第三种方法,你最终得到3个表,父ApplicationUser类将映射到它的具体实现 . 但是,我从来没有用Asp.Net Identity实现这个,所以我不知道它是否会起作用,如果你遇到一些关键的冲突 .

相关问题