.NET Core 2.1 Identity:为每个角色创建一个表 + 网桥 M:M 表 [英] .NET Core 2.1 Identity : Creating a table for each Role + bridge M:M table

查看:20
本文介绍了.NET Core 2.1 Identity:为每个角色创建一个表 + 网桥 M:M 表的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在 .NET Core 2.1 项目中使用 Identity 确定适合我的基于角色的授权的最佳设计时遇到问题.

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

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

所有通用属性都保存在 User 和 ApplicationUser 中,但我仍然需要根据用户的角色与其他表建立不同的关系.

  • 角色教师的用户已链接到 1-N 学校
  • 角色学生中的用户链接到 1-N GroupOfStudents(但不直接链接到学校)
  • 角色家长的用户与 1-N 学生(但不与学校)相关联
  • ...

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

就我而言,最佳做法是什么?

Identity 的功能是否有我遗漏的地方?

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

我正在考虑使用桥接表"将用户链接到每个角色的其他表.ApplicationUser 和桥表之间是多对多的关系,桥表和每个角色的单个表之间是 0-1 关系.但这也无济于事,因为每条记录都会产生相同数量的空字段.

我对 .NET Core 尤其是 Identity 还很陌生,我可能缺少一些关键字来进行有效的研究,因为在我看来它是一个非常基本的系统(要求中没有什么特别花哨的东西).

>

感谢阅读!

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

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

公共类 ApplicationUser : IdentityUser{公共字符串 CustomTag { 获取;放;}公共字符串 CustomTagBis { 获取;放;}}公开课老师:ApplicationUser{公共字符串教师身份编号 { 获取;放;}公共 ICollection<课程>课程{得到;放;}}公共类学生:ApplicationUser{公共 ICollection组 { 得到;放;}}公共类父级:ApplicationUser{公共 ICollection孩子{得到;放;}}公开课{公共 int Id { 获取;放;}公共字符串标题{获取;放;}公共字符串类别{获取;放;}}公开课学生组{公共 int Id { 获取;放;}公共字符串名称 { 获取;放;}}

这会为包含所有属性的用户创建一个包含一张大表的数据库:

生成用户表

我可以使用它,它会起作用.如果用户需要担任不同的角色,他可以填充任何这些可为空的字段.

我担心的是,对于每条记录,我都会有大量的不适当的字段"保持空白.假设在 1000 个用户中,80% 的用户是学生.包含 800 行的结果是什么:- 一个空的 ParentId FK- 一个空的教师身份号码

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

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

Table-Per-Hierarchy 方法图

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

实体类型‘IdentityUserLogin’需要定义一个主键."

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

base.OnModelCreating(builder);

导致有这个代码:

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

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

经过一番研究,我发现

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

<小时>

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

这样的设计是否符合我的要求并且有效?有效,我的意思是,将教师实体链接到角色而不是用户感觉很奇怪.但在某种程度上,教师实体代表了用户在担任教师角色时需要的特征.

对实体的作用

我还不太确定如何使用 EF 核心实现这一点,以及覆盖 IdentityRole 类将如何影响 Identity 功能.我正在做,但还没有弄清楚.

解决方案

我建议您利用 asp.net core 的新功能和新的 Identity 框架.有很多关于安全性.

您可以使用基于策略的 安全,但就您而言 基于资源的安全性似乎更合适.

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

因为将 ApplicationUser 表放在您的业务上下文"中意味着您正在直接访问身份上下文.这不是您应该使用 Identity 的方式.使用 UserManager 进行 IdentityUser 相关查询.

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

此处查看我的答案关于更详细设计的想法.

将像 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", "学生");

尽管我认为在您的情况下,将信息存储在学校环境中可能会更好.

最重要的是,保持身份上下文不变,而是将表格添加到学校上下文中.您不必创建两个数据库,只需不要添加跨上下文关系.唯一将两者结合起来的是 UserId.但是您不需要为此建立实际的数据库关系.

使用 UserManager 等进行身份查询和应用程序的学校环境.当不用于身份验证时,您不应使用身份上下文.

<小时>

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

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

当用户出现在表格中时,这意味着该用户是学校 x 的学生或学校 y 的老师.不需要身份上下文中的角色.

通过导航属性,您可以轻松确定角色"并访问该角色"的字段.

I'm having issues in figuring out the best design that fits my needs regarding a Role based authorizations using Identity in a .NET Core 2.1 project.

I already extended the User class from Identity with an ApplicationUser class. I need 5 different roles to control the access to the different features of the app :

Admin, Teacher, Student, Parent and Supervisor

All the common attributes are kept in User and ApplicationUser but I still require different relationships to other tables depending of the User's Role.

  • User in Role Teacher is linked to 1-N School
  • User in Role Student is linked to 1-N GroupOfStudents (but not to a School directly)
  • User in Role Parent is linked to 1-N Student (but not to a School)
  • ...

The other requirement is that a User must be able to be in 1-N Role.

What would be the best practice in my case?

Is there something I'm missing in the features of Identity?

My idea at first was to use nullable FK, but as the number of role increased, it doesn't look like a good idea to have so many empty fields for all those records.

I was thinking of using a "bridge table" to link a User to other tables for each role. Have a many-to-many relationship between ApplicationUser and the bridge table nd a 0-1 relationship between the bridge table and individual tables for each role. But that's not really helping either since every record will produce the same amount of empty fields.

I'm fairly new with .NET Core and especially Identity, I'm probably missing some keywords to make an effective research because it looks to me that it's a really basic system (nothing really fancy in the requirements).

Thanks for reading !

EDIT : I don't really have a error right now since I'm trying to figure out the best practice before going deeper in the project. Since it's the first time I face that kind of requirement, I'm trying to find documentation on what are the pros/cons.

I followed Marco's idea and used inheritance for my role based models as it was my first idea. I hope it will help understand my concern.

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

This creates the database having one big table for the User containing all the attributes :

User table generated

I can use this and it will work. A user can have any of those nullable fields filled if he requires to be in different role.

My concern is that for each record I will have a huge number of "inappropriate fields" that will remain empty. Let's say that on 1000 users 80% of the users are Students. What are the consequences of having 800 lines containing : - an empty ParentId FK - an empty TeacherIdentificationNumber

And this is just a small piece of the content of the models. It doesn't "feel" right, am I wrong?

Isn't there a better way to design the entities so that the table User only contains the common attributes to all users (as it is supposed to?) and still be able to link each user to another table that will link the User to 1-N tables Teacher/Student/Parent/... table?

Diagram of the Table-Per-Hierarchy approach

EDIT 2: Using the answer of Marco, I tried to use the Table-Per-Type approach. When modifying my context to implement the Table-Per-Type approach, I encountered this error when I wanted to add a migration :

"The entity type 'IdentityUserLogin' requires a primary key to be defined."

I believe this happens because I removed :

base.OnModelCreating(builder);

Resulting in having this code :

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

I believe those identity keys are mapped in the base.OneModelCreating. But Even if I Uncomment that line, I keep the same result in my database.

After some research, I found this article that helped me go through the process of creating Table-per-type models and apply a migration.

Using that approach, I have a schema that looks like this : Table-Per-Type approach

Correct me if I'm wrong, but both Techniques fits my requirements and it is more about the preference of design? It doesn't have big consequence in the architecture nor the identity features?


For a third option, I was thinking to use a different approach but I'm not too sure about it.

Does a design like this could fit my requirements and is it valid? By valid, I mean, it feels weird to link a teacher entity to a Role and not to a User. But in a way, the teacher entity represent the features that a User will need when in the teacher role.

Role to Entities

I'm not yet too sure of how to implement this with EF core and how overriding the IdentityRole class will affect the Identity features. I'm on it but haven't figured it out yet.

解决方案

I suggest you take advantage of the new features of asp.net core and the new Identity framework. There is a lot of documentation about security.

You can use policy based security, but in your case resource-based security seems more appropriate.

The best approach is to not mix contexts. Keep a seperation of concerns: Identity context (using UserManager) and business context (school, your DbContext).

Because putting the ApplicationUser table in your 'business context' means that you are directly accessing the Identity context. This is not the way you should use Identity. Use the UserManager for IdentityUser related queries.

In order to make it work, instead of inheriting the ApplicationUser table, create a user table in your school context. It is not a copy but a new table. In fact the only thing in common is the UserId field.

Check my answer here for thoughts about a more detailed design.

Move fields like TeacherIdentificationNumber out of the ApplicationUser. You can either add this as claim to the user (AspNetUserClaims table):

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

or store it in the school context.

Also instead of roles consider to use claims, where you can distinguish the claims by type name (e.g. http://school1.myapp.com/role):

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

Though I think in your case it may be better to store the information in the school context.

The bottom line, keep the Identity context as is and add tables to the school context instead. You don't have to create two databases, just don't add cross-context relations. The only thing that binds the two is the UserId. But you don't need an actual database relation for that.

Use UserManager, etc. for Identity queries and your school context for your application. When not for authentication you should not use the Identity context.


Now to the design, create one user table that has a matching UserId field to link the current user. Add fields like name, etc only when you want to show this (on report).

Add a table for Student, Teacher, etc. where you use a composite key: School.Id, User.Id. Or add a common Id and use a unique constraint on the combination of School.Id, User.Id.

When a user is present in the table this means that the user is a student at school x or teacher at school y. No need for roles in the Identity context.

With the navigation properties you can easily determine the 'role' and access the fields of that 'role'.

这篇关于.NET Core 2.1 Identity:为每个角色创建一个表 + 网桥 M:M 表的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆