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

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

问题描述

在解决.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的想法,并使用了继承我的榜样是我的第一个想法。

 公共类ApplicationUser:IdentityUser 
{
公共字符串CustomTag {得到;组; }
公用字串CustomTagBis {get;组; }
}
公共班级教师:ApplicationUser
{
公共字符串TeacherIdentificationNumber {get;组; }
公共ICollection< Course>课程{获得;组; }
}
公共班级学生:ApplicationUser
{
公共ICollection< StudentGroup>群组{get;组; }
}
公共类父类:ApplicationUser
{
公共ICollection< Student>孩子们{组; }
}
公共课程课程
{
public int Id {get;组; }
公用字串Title {get;组; }
公共字符串Category {get;组; }
}
公共班级StudentGroup
{
public int Id {get;组; }
公共字符串Name {get;组; }
}

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



生成的用户表



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



我担心的是,对于每条记录,我都会拥有大量保留为空的不适当字段的数量。
假设有1000位用户,其中80%是学生。
包含800行包含什么内容:
-空的ParentId FK
-空的TeacherIdentificationNumber



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



没有更好的方法来设计实体,以便用户表仅包含所有用户的共同属性(应该是?),并且仍然能够将每个用户链接到另一个表,该表会将User链接到1-N表Teacher / Student / Parent / ...表?



逐层表方法图



编辑2:
使用Marco的答案,我尝试使用Table-Per-Type方法。
修改我的上下文以实现逐表类型方法时,当我想添加迁移时遇到此错误:



实体类型' IdentityUserLogin'需要定义一个主键。



我相信发生这种情况是因为我删除了:

  base.OnModelCreating(builder); 

产生此代码:

 受保护的覆盖无效OnModelCreating(ModelBuilder builder)
{
//base.OnModelCreating(builder);
builder.Entity< Student>()。ToTable( Student);
builder.Entity< Parent>()。ToTable( Parent);
builder.Entity< Teacher>()。ToTable( Teacher);
}

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



经过研究,我发现



如果我错了,请纠正我,但是这两种技术都符合我的要求,而更多地是关于设计的偏爱吗?






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



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



对实体的作用



我还不太确定如何使用EF内核实现此功能,以及重写IdentityRole类将如何影响Identity功能。我正在使用它,但尚未弄清楚。

解决方案

我建议您利用ASP的新功能.net核心和新的Identity框架。关于安全性。



您可以使用基于策略的安全性,但在您的情况下基于资源的安全性似乎更合适。



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



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



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



检查我的答案此处,以获取有关详细设计的想法。



移动诸如TeacherIdentificationNumber之类的字段脱离ApplicationUser。您可以将其作为声明添加到用户(AspNetUserClaims表):

 新声明( http://school1.myapp .com / TeacherIdentificationNumber,123); 

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



也可以考虑使用声明,而不是角色,您可以在其中按类型名称区分声明(例如http:// school1 .myapp.com / role):

 新索赔( http://school1.myapp.com/role,老师); 
new Claim( http://school2.myapp.com/role,学生);

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



最重要的是,保持Identity上下文不变,并向学校上下文中添加表。您不必创建两个数据库,也不必添加跨上下文关系。绑定两者的唯一内容是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身份:为每个Role + bridge M:M表创建一个表的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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