.NET Core 2.1身份:为每个角色+桥M:M表创建一个表

时间:2018-09-29 18:51:53

标签: .net-core entity-framework-core asp.net-core-identity

在解决有关.NET Core 2.1项目中使用Identity的基于角色的授权的最佳设计时,我遇到了问题。

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

管理员,老师,学生,父母和主管

所有常见属性都保留在User和ApplicationUser中,但根据用户角色,我仍然需要与其他表的不同关系。

  • 角色老师中的用户已链接到1-N学校
  • “角色学生”中的用户链接到1-N GroupOfStudents(但不直接链接到学校)
  • “角色家长”中的用户已链接到1-N学生(​​但未链接到学校)
  • ...

另一个要求是用户必须具有1-N角色。

在我的情况下,最佳做法是什么?

身份功能是否缺少某些内容?

起初我的想法是使用可为空的FK,但是随着角色数量的增加,对所有这些记录使用如此多的空字段似乎不是一个好主意。

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

我对.NET Core尤其是Identity刚起步,我可能缺少一些关键词来进行有效的研究,因为在我看来,这是一个非常基本的系统(对要求的要求并不高)。

感谢阅读!

编辑: 我现在并没有真正的错误,因为在尝试深入项目之前,我正在尝试找出最佳实践。由于这是我第一次遇到这种要求,因此我试图查找有关优点/缺点的文档。

我遵循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; }
}

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

生成用户表 image

我可以使用它,它将起作用。 如果用户需要扮演不同的角色,则可以填充任何这些可为空的字段。

我担心的是,对于每条记录,我都会有大量“不适当的字段”保持空白。 假设有1000位用户,其中80%是学生。 800行包含以下内容会带来什么后果: -空的ParentId FK -一个空的TeacherIdentificationNumber

这只是模型内容的一小部分。 感觉不对,我错了吗?

没有更好的方法来设计实体,以便表User仅包含所有用户的公共属性(应该如此),并且仍然能够将每个用户链接到另一个将链接用户的表。用户到1-N表教师/学生/父母/ ...表?

逐层表方法图 image

编辑2: 使用Marco的答案,我尝试使用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帮助我完成了创建每个类型的表格模型和应用迁移的过程。

使用这种方法,我有一个如下所示的架构: 每种类型的表格方法 image

如果我弄错了,请纠正我,但是两种技巧都可以满足我的要求,而这更多地与设计偏好有关?它对体系结构或身份功能都没有太大影响?


对于第三种选择,我当时正在考虑使用其他方法,但是我不太确定。

这样的设计是否可以满足我的要求,并且有效吗? 有效,我是说,将老师实体链接到角色而不是用户链接,感觉很奇怪。但是在某种程度上,教师实体表示用户在担任教师角色时将需要的功能。

对实体的作用 image

我还不太确定如何使用EF核心实现此功能,以及重写IdentityRole类将如何影响Identity功能。我在上面,但还没弄清楚。

2 个答案:

答案 0 :(得分:2)

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

您可以使用policy based安全性,但对于您来说,resource-based安全性似乎更合适。

最好的方法是不要混合上下文。将关注点分开:身份上下文(使用UserManager)和业务上下文(学校,您的DbContext)。

因为将ApplicationUser表放在“业务上下文”中意味着您正在直接访问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");

尽管我认为您的情况最好将信息存储在学校环境中。

最重要的是,保持Identity上下文不变,然后将表格添加到school上下文中。您不必创建两个数据库,而不必添加跨上下文关系。绑定两者的唯一内容是UserId。但是您不需要为此建立实际的数据库关系。

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


现在进行设计,创建一个用户表,该表具有匹配的UserId字段以链接当前用户。仅当您想在报告中显示此名称时,才添加名称等字段。

为使用组合键的Student,Teacher等添加表:School.Id,User.Id。或添加一个通用ID并对School.Id,User.Id的组合使用唯一约束。

当用户出现在表中时,这表示该用户是x学校的学生或y学校的老师。无需在身份上下文中扮演角色。

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

答案 1 :(得分:1)

您所做的完全符合您的要求。您当前实现的称为Table-Per-Hierarchy。这是Entity Framework发现模型时的默认方法。

另一种方法是Table-Per-Type。在这种情况下,实体框架将创建4个表。

  1. 用户表
  2. 学生桌
  3. 教师桌
  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来实现此功能,因此我不知道它是否会或是否会起作用,以及您是否会遇到一些关键冲突。