Search code examples
c#mysqlasp.net-coreentity-framework-coreasp.net-core-identity

.NET Core 2.1 Identity get all users with their associated roles


I'm trying to pull out all my Identity users and their associated roles for a user management admin page. I thought this would be reasonably easy but apparently not. I've tried following the following solution: https://stackoverflow.com/a/43562544/5392786 but it hasn't worked out so far.

Here is what I have so far:

ApplicationUser:

public class ApplicationUser : IdentityUser
{
    public List<IdentityUserRole<string>> Roles { get; set; }
}

DBContext

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

Startup Identity code

services.AddIdentity<ApplicationUser, IdentityRole>(options => options.Stores.MaxLengthForKeys = 128)
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();

Razor Page where I want to display the list:

public class IndexModel : PageModel
{
    private readonly UserManager<ApplicationUser> userManager;

    public IndexModel(UserManager<ApplicationUser> userManager)
    {
        this.userManager = userManager;
    }

    public IEnumerable<ApplicationUser> Users { get; set; }

    public void OnGetAsync()
    {
        this.Users = userManager.Users.Include(u => u.Roles).ToList();
    }
}

I get the following error when calling userManager.Users.Include(u => u.Roles).ToList();:

MySql.Data.MySqlClient.MySqlException: 'Unknown column 'u.Roles.ApplicationUserId' in 'field list''


Solution

  • I have now implemented the following solution.

    As CodeNotFound pointed out in the comments, IdentityUser used to have a Roles property. This is no longer the case in .NET Core. This comment/issue on GitHub seems to be the current solution for .Net Core. I have attempted to implemented it with the following code:

    ApplicationUser

    public class ApplicationUser : IdentityUser
    {
        public ICollection<ApplicationUserRole> UserRoles { get; set; }
    }
    

    ApplicationUserRole

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

    ApplicationRole

    public class ApplicationRole : IdentityRole
    {
        public ICollection<ApplicationUserRole> UserRoles { get; set; }
    }
    

    DBContext

    public class ApplicationDbContext
        : IdentityDbContext<ApplicationUser, ApplicationRole, string, IdentityUserClaim<string>,
        ApplicationUserRole, IdentityUserLogin<string>,
        IdentityRoleClaim<string>, IdentityUserToken<string>>
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }
    
        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);
    
            builder.Entity<ApplicationUserRole>(userRole =>
            {
                userRole.HasKey(ur => new { ur.UserId, ur.RoleId });
    
                userRole.HasOne(ur => ur.Role)
                    .WithMany(r => r.UserRoles)
                    .HasForeignKey(ur => ur.RoleId)
                    .IsRequired();
    
                userRole.HasOne(ur => ur.User)
                    .WithMany(r => r.UserRoles)
                    .HasForeignKey(ur => ur.UserId)
                    .IsRequired();
            });
        }
    }
    

    Startup

    services.AddIdentity<ApplicationUser, ApplicationRole>(options => options.Stores.MaxLengthForKeys = 128)
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();
    

    Finally, make sure when you're using it that you eagerly load the User's UserRoles, and then the UserRole's Role like so:

    this.Users = userManager.Users.Include(u => u.UserRoles).ThenInclude(ur => ur.Role).ToList();
    

    I had an issue where the Role property of each UserRole was null and this was resolved by adding in the .ThenInclude(ur => ur.Role) part.

    Microsoft doc on multi-level eager loading: https://learn.microsoft.com/en-us/ef/core/querying/related-data#including-multiple-levels

    ASP Core 2.2 update

    Inherent from IdentityUserRole<Guid> not string You may also need to remove the code in the ModelBuilder to get migrations working.