Search code examples
c#asp.net-coreasp.net-identitydbcontext

How create two different user table with IdentityUser


Project in a core 2.2

I have two classes that inherit IdentityUser, the first one of them

 public class DeveloperModel : IdentityUser
{
    [Required(AllowEmptyStrings = false)]
    public string FirstName { get; set; }

    [Required(AllowEmptyStrings = false)]
    public string SecondName { get; set; }

    [Required(AllowEmptyStrings = false)]
    public string Company { get; set; }

    [Required(AllowEmptyStrings = false)]
    public string CompanyName { get; set; }

    [Required(AllowEmptyStrings = false)]
    public string CompanyAdress { get; set; }

    [Required(AllowEmptyStrings = false)]
    public string CompanyEmail { get; set; }

    [Required(AllowEmptyStrings = false)]
    public string CompanyPhoneNumber { get; set; }
}

and the second of them

public class UserModel : IdentityUser
{
    [Required(AllowEmptyStrings = false)]
    public string FirstName { get; set; }

    [Required(AllowEmptyStrings = false)]
    public string SecondName { get; set; }

}

my context for these two classes

 public class EFDbContext : IdentityDbContext
{
    public EFDbContext(DbContextOptions<EFDbContext> options) : base(options)
    {
        
    }

    public DbSet<UserModel> UserModels { get; set; }
    public DbSet<DeveloperModel> DeveloperModels { get; set; }
}

and for the end of the migration I take that

[Id]
  ,[UserName]
  ,[NormalizedUserName]
  ,[Email]
  ,[NormalizedEmail]
  ,[EmailConfirmed]
  ,[PasswordHash]
  ,[SecurityStamp]
  ,[ConcurrencyStamp]
  ,[PhoneNumber]
  ,[PhoneNumberConfirmed]
  ,[TwoFactorEnabled]
  ,[LockoutEnd]
  ,[LockoutEnabled]
  ,[AccessFailedCount]
  ,[FirstName]
  ,[SecondName]
  ,[Company]
  ,[CompanyAdress]
  ,[CompanyEmail]
  ,[CompanyName]
  ,[CompanyPhoneNumber]
  ,**[UserModel_FirstName]**
  ,**[UserModel_SecondName]**
  ,[Discriminator]

I mark by ** ** to show the problem, that in usual rule for .Net inherit I take 1 table for these two classes. However, can I create two tables for each of the classes, with identityUser properties?

for my task, I need two tables. I don't know how to realize my idea


Solution

  • I think that you have to inject your IdentityDbContext with your custom models.

    You can map your models to the corresponding tables. example

    builder.Entity<UserModel>(b =>
    {
        // Primary key
        b.HasKey(u => u.Id);
    
        //map properties
        b.Property(u => u.FirstName ).HasName("FirstName").IsUnique();
        b.Property(u => u.SecondName ).HasName("SecondName");
    
        // Maps to the AspNetUsers table
        b.ToTable("AspNetUsers");
    
    });
    
    builder.Entity<UserModelSplit>(b =>
    {
        // Primary key
        b.HasKey(u => u.Id);
    
         //map properties
        b.Property(u => u.UserName ).HasName("UserName").IsUnique();
        b.Property(u => u.NormalizedUserName ).HasName("NormalizedUserName");
    ...
    ...
    ...
        // Maps to the AspNetUsers table
        b.ToTable("AspNetUsersSplit");
    
    });
    
    public class EFDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string>  
        {  
            public EFDbContext (DbContextOptions<EFDbContext > options) : base(options)  
            {  
    
            }  
        }  
    

    example

    public class ApplicationUser : IdentityUser
    
    public class Instructor : ApplicationUser
    
    public class Student : ApplicationUser
    

    By default, Entity Framework will create the one table for ApplicationUser and add a Discriminator column to it. This column will have one of three possible values: "ApplicationUser", "Instructor", and "Student". When EF reads from this table, it will use this column to instantiate the right class. This is what's known as single-table inheritance (STI) or alternatively as table-per-hierarchy (TPH). The main downside to this approach is that all of the properties for all of the classes must be represented on the same table. If you're creating a new Student for example, the columns for an Instructor would still be on the record, only with nulls or defaults for those values. This also means that you can't enforce a property on something like Instructor be required at the database level, as that would prevent saving ApplicationUser and Student instances which are unable to provide those values. In other words, all your properties on your derived classes must be nullable. However, you can always still enforce something like a property being required for the purpose of a form using view models.

    If you really want to have separate tables, you can somewhat achieve that goal by changing the inheritance strategy to what's called table-per-type (TPT). What this will do is keep the table for ApplicationUser, but add two additional tables, one each for Instructor and Student. However, all the core properties, foreign keys, etc. will be on the table for ApplicationUser, since that is where those are defined. The tables for Instructor and Student would house only properties that are defined on those classes (if any) and a foreign key to the table for ApplicationUser. When querying, EF will then do joins to bring in the data from all of these tables and instantiate the appropriate classes with the appropriate data. Some purists like this approach better as keeps the data normalized in the database. However, it's necessarily heavier on the query side because of the joins.

    One last word of caution, as this trips people up constantly dealing with inheritance with Identity. The UserManager class is a generic class (UserManager). The default instance in AccountController, for example, is an instance of UserManager. As a result, if you use that instance, all users returned from queries will be ApplicationUser instances, regardless of the value of the Discriminator column. To get Instructor instances, you would need to instantiate UserManager and use that for your Instructor-related queries.

    Identity in an ASP.NET Core project