Не получается создать кастомную модель БД в IdentityDbContext

Хочу создать свою модель БД для Identity.

Допустим есть Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUser, мне хочется унаследовать от него свой тип, где будут содержаться дополнительные поля для моей BL (например, CustomUser).

[Table("user_tbl")]
public class CustomUser : IdentityUser<int>
{
    virtual public string CustomProperty { get; set; }

    virtual public ICollection<CustomUserRole> UserRoles { get; set; }
    virtual public ICollection<CustomUserClaim> UserClaims { get; set; }
    virtual public ICollection<CustomUserLogin> UserLogins { get; set; }
    virtual public ICollection<CustomUserToken> UserTokens { get; set; }
}

Как вы заметили, тут есть ещё коллекции кастомных ролей, клеймов и т.д. Они так же унаследованы от типов из Microsoft.AspNetCore.Identity.EntityFrameworkCore

[Table("userClaims_tbl")]
public class CustomUserClaim : IdentityUserClaim<int>
{
    virtual public CustomUser User { get; set; }
}

[Table("userLogins_tbl")]
public class CustomUserLogin : IdentityUserLogin<int>
{
    virtual public CustomUser User { get; set; }
}

[Table("userTokens_tbl")]
public class CustomUserToken : IdentityUserToken<int>
{
    virtual public CustomUser User { get; set; }
}

public class CustomRoleClaim : IdentityRoleClaim<int>
{
    virtual public CustomRole Role { get; set; }
}

[Table("userRoles_tbl")]
public class CustomUserRole : IdentityUserRole<int>
{
    virtual public CustomUser User { get; set; }

    virtual public CustomRole Role { get; set; }
}

[Table("roles_tbl")]
public class CustomRole : IdentityRole<int>
{
    virtual public string CustomProperty { get; set; }

    virtual public ICollection<CustomUserRole> UserRoles { get; set; }

    virtual public ICollection<CustomRoleClaim> RoleClaims { get; set; }
}

После этого я создаю контекст базы данных и в методе OnModelCreating() создаю связи между таблицами:

public class CustomDbContext : IdentityDbContext<CustomUser, CustomRole, int,
    CustomUserClaim, CustomUserRole, CustomUserLogin, CustomRoleClaim, CustomUserToken>
{
    public CustomDbContext() =>
        Database.EnsureCreatedAsync();

    public CustomDbContext(DbContextOptions options) : base(options) =>
        Database.EnsureCreatedAsync();

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        builder.Entity<CustomUser>(usr =>
        {
            usr.HasMany<CustomUserClaim>()
                .WithOne(prop => prop.User)
                .HasForeignKey("user_id")
                .IsRequired();

            usr.HasMany<CustomUserLogin>()
                .WithOne(prop => prop.User)
                .HasForeignKey("user_id")
                .IsRequired();

            usr.HasMany<CustomUserToken>()
                .WithOne(prop => prop.User)
                .HasForeignKey("user_id")
                .IsRequired();

            usr.HasMany<CustomUserRole>()
                .WithOne(prop => prop.User)
                .HasForeignKey("user_id")
                .IsRequired();
        });

        builder.Entity<CustomRole>(role =>
        {
            role.HasMany<CustomUserRole>()
                .WithOne(prop => prop.Role)
                .HasForeignKey("role_id")
                .IsRequired();

            role.HasMany<CustomRoleClaim>()
                .WithOne(prop => prop.Role)
                .HasForeignKey("role_id")
                .IsRequired();
        });
    }
}

Далее в классе Startup в методе ConfigureServices добавляю сервисы:

public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<CustomDbContext>(options =>
            options.UseSqlServer(
                Configuration.GetConnectionString("DefaultConnection")));

        services.AddIdentity<CustomUser, CustomRole>(options => options.SignIn.RequireConfirmedAccount = true)
            .AddDefaultTokenProviders()
            .AddDefaultUI()
            .AddEntityFrameworkStores<CustomDbContext>();
        
        services.AddRazorPages();
    }

После всего мне нужно мигрировать это всё на настоящую БД:

Add-Migration "init"

Update-Database

В итоге получаю это: введите сюда описание изображения

Самое интересное, когда я создаю БД на дефолтных типах Identity, всё получается без проблем. Что тут не так? Я уже переписываю код как на msdn, но ничего не получается.


Ответы (1 шт):

Автор решения: Никита

Вообщем, решение проблемы лежит в переопределении ВСЕЙ модели в методе OnModelCreating()

Если мы наследуем наш контекст данных так:

public class DefaultDbContext : IdentityDbContext<CustomUser, CustomRole, int,
            CustomUserClaim, CustomUserRole, CustomUserLogin, CustomRoleClaim, CustomUserToken>
{
    //...
}

То и метод OnModelCreating() должен описать ВСЕ типы, с которыми работает контекст:

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        builder.Entity<CustomUser>(user =>
        {
            user.ToTable("users_tbl");

            user.HasKey(prop => prop.Id);

            // if neccessary cusomize CustomProperty
            // user.Property(prop => prop.CustomProperty).HasMaxLength...

            user.HasMany<CustomUserRole>()
                .WithOne(prop => prop.User)
                .HasForeignKey(prop => prop.UserId)
                .IsRequired();

            user.HasMany(prop => prop.UserClaims)
                .WithOne(prop => prop.User)
                .HasForeignKey(prop => prop.UserId)
                .IsRequired();

            user.HasMany(prop => prop.UserLogins)
                .WithOne(prop => prop.User)
                .HasForeignKey(prop => prop.UserId)
                .IsRequired();

            user.HasMany(prop => prop.UserTokens)
                .WithOne(prop => prop.User)
                .HasForeignKey(prop => prop.UserId)
                .IsRequired();
        });

        builder.Entity<CustomRole>(role =>
        {
            role.ToTable("roles_tbl");

            // if neccessary cusomize CustomProperty
            // user.Property(prop => prop.CustomProperty).HasMaxLength...

            role.HasKey(prop => prop.Id);

            role.HasMany(prop => prop.UserRoles)
                .WithOne(prop => prop.Role)
                .HasForeignKey(prop => prop.RoleId)
                .IsRequired();

            role.HasMany(prop => prop.RoleClaims)
                .WithOne(prop => prop.Role)
                .HasForeignKey(prop => prop.RoleId)
                .IsRequired();
        });

        builder.Entity<CustomUserRole>(ur =>
        {
            ur.HasKey(prop => new { prop.RoleId, prop.UserId });

            ur.ToTable("userRoles_tbl");
        });

        builder.Entity<CustomUserClaim>(uc =>
        {
            uc.HasKey(prop => prop.Id);

            uc.ToTable("userClaims_tbl");
        });

        builder.Entity<CustomUserLogin>(ul =>
        {
            ul.HasKey(prop => prop.Id);

            ul.ToTable("userLogins_tbl");
        });

        builder.Entity<CustomUserToken>(ut =>
        {
            ut.HasKey(prop => prop.Id);

            ut.ToTable("userTokens_tbl");
        });

        builder.Entity<CustomRoleClaim>(rc =>
        {
            rc.HasKey(prop => prop.Id);

            rc.ToTable("roleClaims_tbl");
        });

    }

Так же, вовсе не нужно ставить модификатор virtual для каждого свойства:

[Table("user_tbl")]
public class CustomUser : IdentityUser<int>
{
    public string CustomProperty { get; set; }

    public ICollection<CustomUserRole> UserRoles { get; set; }
    public ICollection<CustomUserClaim> UserClaims { get; set; }
    public ICollection<CustomUserLogin> UserLogins { get; set; }
    public ICollection<CustomUserToken> UserTokens { get; set; }
}

[Table("userClaims_tbl")]
public class CustomUserClaim : IdentityUserClaim<int>
{
    public CustomUser User { get; set; }
}

[Table("userLogins_tbl")]
public class CustomUserLogin : IdentityUserLogin<int>
{
    public int Id { get; set; }

    public CustomUser User { get; set; }
}

[Table("userTokens_tbl")]
public class CustomUserToken : IdentityUserToken<int>
{
    public int Id { get; set; }

    public CustomUser User { get; set; }
}

[Table("roleClaims_tbl")]
public class CustomRoleClaim : IdentityRoleClaim<int>
{
    public CustomRole Role { get; set; }
}

Атрибуты, как я понял, игнорируются, так что все настройки придется описывать во FluentAPI

Вот что получилось: введите сюда описание изображения

→ Ссылка