EF Core 2.0 Identity - Adding navigation properties
Asked Answered
I

3

9

In EF Core 2.0 Identity navigation properties are not included by default, so after upgrading, I added them. So for Many-To-Many relationship between User and Role, and One-To-Many relationship between Role and RoleClaim, I added following navigation properties:

public class User : IdentityUser
{
    [Required]
    public string Name { get; set; }

    public virtual ICollection<IdentityUserRole<string>> Roles { get; set; }
}

public class Role : IdentityRole
{
    [Required]
    public string Name { get; set; }

    public virtual ICollection<IdentityRoleClaim<string>> Claims { get; set;}
}

Surprisingly it adds an additional RoleId1 key to AspNetRoleClaims table and UserId1 to AspNetUserRoles tables and all the get queries actually use new keys instead of RoleId and UserId which are also present.

Internment answered 24/8, 2017 at 13:55 Comment(1)
See the answer on this thread https://mcmap.net/q/765214/-ef-core-2-how-to-include-roles-navigation-property-on-identityuserElspet
S
11

I do not know why, there are not these useful navigation properties. I want to list users with their roles.

So I did the follow:

public class ApplicationUser : IdentityUser
{
    public virtual ICollection<ApplicationUserRole> UserRoles { get; } = new List<ApplicationUserRole>();
}

public class ApplicationUserRole : IdentityUserRole<string>
{
    public virtual ApplicationUser User { get; set; }
    public virtual ApplicationRole Role { get; set; }
}

public class ApplicationRole : IdentityRole<string>
{
    public ApplicationRole(){ }

    public ApplicationRole(string roleName)
        : base(roleName)
    {
    }

    public virtual ICollection<ApplicationUserRole> UserRoles { get; } = new List<ApplicationUserRole>();
}

This creates the navigation, but it creates additional columns like RoleId1 and Discriminator. So, I added the following according to Add IdentityUser POCO Navigation Properties.

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

    builder.Entity<ApplicationUser>()
        .HasMany(e => e.UserRoles)
        .WithOne()
        .HasForeignKey(e => e.UserId)
        .IsRequired()
        .OnDelete(DeleteBehavior.Cascade);

    builder.Entity<ApplicationUserRole>()
        .HasOne(e => e.User)
        .WithMany(e => e.UserRoles)
        .HasForeignKey(e => e.UserId);

    builder.Entity<ApplicationUserRole>()
        .HasOne(e => e.Role)
        .WithMany(e => e.UserRoles)
        .HasForeignKey(e => e.RoleId);
}

But I still have both columns RoleId1 and Discriminator. After that, I replace with the new ApplicationRole class in ApplicationDbContext, DI configuration service and DB seed.

public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string, IdentityUserClaim<string>
    , ApplicationUserRole, IdentityUserLogin<string>, IdentityRoleClaim<string>, IdentityUserToken<string>>
{
    ...
}

public void ConfigureServices(IServiceCollection services)
{
   ...
   services.AddIdentity<ApplicationUser, ApplicationRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();
   ...
}

public DbInitializer(
        ApplicationDbContext context,
        UserManager<ApplicationUser> userManager,
        RoleManager<ApplicationRole> roleManager)
    {
        _context = context;
        _userManager = userManager;
        _roleManager = roleManager;
    }

public async void Initialize()
    {
        _context.Database.EnsureCreated();

        if (!_context.Roles.Any(r => r.Name == SharedConstants.Role.ADMINISTRATOR))
            await _roleManager.CreateAsync(new ApplicationRole(SharedConstants.Role.ADMINISTRATOR));
    }            

Also, I could navigate and get the first name of role.

ctx.Users.Select(e => new
            {
                e.Id,
                e.UserName,
                e.Email,
                e.PhoneNumber,
                Roles = e.UserRoles.Select(i => i.Role.Name).ToList()
            }).ToList();

I hope this give you a clue for Claims navigation property.

Suchlike answered 22/9, 2017 at 0:17 Comment(1)
Thanks for the very thorough answer. I just hit up against this, followed your advice, and it's good to go now!Caleb
E
7

I've encountered the same issue, in my case the problem was caused because I had put the call to base.OnModelCreating(builder) at the bottom of OnModelCreating. Moving base.OnModelCreating(builder) at the very top solved the issue (no duplicate columns and FKs).

enter image description here

Thanks to this GitHub issue

Ensample answered 5/6, 2019 at 14:2 Comment(2)
This solved the issue for me. @Suchlike has a good answer for 'set-up'. However, this answer above is what solved my issue ^^Hafler
Old topic but solved my problem as well, thanks a lot!Undergo
I
0

I encountered with same issue and this is solution.

You have to tell Ef on which navigation property you are going to have OneToMany Relation.

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


        CreateUserModel(modelBuilder.Entity<User>());
        CreateRoleModel(modelBuilder.Entity<Role>());

    }


    private void CreateRoleModel(EntityTypeBuilder<Role> entityTypeBuilder)
    {
        entityTypeBuilder.HasMany(role => role.UserRoles).
            WithOne(**e=> e.Role**).
            HasForeignKey(userRole => userRole.RoleId).
            IsRequired()
            .OnDelete(DeleteBehavior.Cascade);
    }

    private void CreateUserModel(EntityTypeBuilder<User> entityTypeBuilder)
    {
        entityTypeBuilder.HasMany(user => user.UserRoles).
            WithOne(**e=>e.User**).
            HasForeignKey(userRole => userRole.UserId).
            IsRequired()
            .OnDelete(DeleteBehavior.Cascade);
    }

You can Also specify navigation property in string like

private void CreateRoleModel(EntityTypeBuilder<Role> entityTypeBuilder)
    {
        entityTypeBuilder.HasMany(role => role.UserRoles).
            WithOne(**"Role"**).
            HasForeignKey(userRole => userRole.RoleId).
            IsRequired()
            .OnDelete(DeleteBehavior.Cascade);
    }

    private void CreateUserModel(EntityTypeBuilder<User> entityTypeBuilder)
    {
        entityTypeBuilder.HasMany(user => user.UserRoles).
            WithOne(**"User"**).
            HasForeignKey(userRole => userRole.UserId).
            IsRequired()
            .OnDelete(DeleteBehavior.Cascade);
    }
Infect answered 26/10, 2018 at 19:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.