简体   繁体   中英

ASP.NET Core 2.2 Entity Inheritance and db reference

https://docs.microsoft.com/en-us/aspnet/core/security/authentication/customize-identity-model?view=aspnetcore-2.2 Explains how to extend the default Identity by subclassing. I have my code setup as explained and it works but now I would like to extend it further because depending on the user role there are different attributes. Eg An ApplicationUser can have favorites, but the extended ApplicationUser has Elements.

I would like to create more subclasses of ApplicationUser. Is that a good idea?

The model is Element. ApplicationUser has a Set of Element and is allowed to favorite them.

ExtendedApplicationUser inherits from ApplicationUser and can add instances of Element that ApplicationUser can favorite.

What should I do?

  1. Do I create a role for ApplicationUser and ExtendedApplicationUser and declare the both sets in ApplicationUser, no subclass.
  2. Or do I subclass ApplicationUser putting each set in each class? My first thought would be to subclass ApplicationUser, but I'm not sure if using roles instead might be a better approach.

Last question, how do I declare the sets in the ApplicationUser entity? Do I use HashSet or DbSet?

Yes, in general, inheritance is the correct way to model this. You need one and only one class that will inherit from IdentityUser . This would be your base ApplicationUser . Then, other types of users would inherit from ApplicationUser .

That said, EF Core's handling of inheritance leaves a lot to be desired. In particular, it only supports TPH (table-per-hierarchy), also known as single-table inheritance. In other words, regardless of what you do, you're only ever going to have one user table, and all the props for all user types will have to reside in that one table. Practically, that just means that you can't have non-nullable properties on your subclasses, since other subclasses would be incapable of providing a value for those, and inserts would then fail. This can be mostly mitigated, though, by using view models, and applying your business rules for what's required and what's not to those instead. You won't get validation at the DB level, but input via forms and such would always be validated.

The above is worth mentioning, given that you can't using more appropriate database modeling strategies such as table-per-type (TPT) or table-per-concrete type (TPC), the use of inheritance is pretty meaningless from a database perspective. Unless, you need to enforce in code that only some set of properties or another can be modified for a particular user type, you can just stick with a single user class. Having multiple user types is not trivial, so there's an argument to be made to keep it simple unless you have a strong need to do otherwise.

In particular, it's easy to mess up the user typing if you're not careful. Single table inheritance works by adding a discriminator column that holds the actual type that is being saved. In other words, if you save an ApplicationUser , then the value will be "ApplicationUser", or if you save an ExtendedApplicationUser , then the value will be "ExtendedApplicationUser". EF uses this value to determine what type it should instantiate. Where it gets tricky is with UserManager<TUser> . The generic type param, not what's passed to methods like CreateAsync determines the type that will be saved. For example, if you new up an ExtendedApplicationUser , but save it via a UserManager<ApplicationUser> instance, then it will be upcast to ApplicationUser and then saved, resulting in "ApplicationUser" being written to the discriminator column. If you need to work with ExtendedApplicationUser , then you'll need a UserManager<ExtendedApplicationUser> instance, and you might end up having multiple such user managers, each for working with a particular type of user. As such, you'll need to be careful that you're using the right user manager for the right scenario.

For you last question, the answer is neither. Collection properties should be either ICollection<TEntity> or List<TEntity> . HashSet can be used to satisfy ICollection<TEntity> when writing to the property, but don't type it as that. DbSet is only for the context.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM