EF Core 2 How To Include Roles Navigation Property On IdentityUser?
See the documentation for 'Migrating Authentication and Identity to ASP.NET Core 2.0', specifically the section 'Add IdentityUser POCO Navigation Properties':
The Entity Framework (EF) Core navigation properties of the base
IdentityUser
POCO (Plain Old CLR Object) have been removed. If your 1.x project used these properties, manually add them back to the 2.0 project:
/// <summary>
/// Navigation property for the roles this user belongs to.
/// </summary>
public virtual ICollection<IdentityUserRole<int>> Roles { get; } = new List<IdentityUserRole<int>>();
To prevent duplicate foreign keys when running EF Core Migrations, add the following to your
IdentityDbContext
class'OnModelCreating
method (after thebase.OnModelCreating();
call):
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
builder.Entity<ApplicationUser>()
.HasMany(e => e.Roles)
.WithOne()
.HasForeignKey(e => e.UserId)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
}
Edit
The above will only satisfy the task of accessing the role Ids held against a user via the IdentityUserRole
link table. To access the role entity itself via a navigation property, you would need to add another navigation property (this time against an entity inheriting from IdentityUserRole
). See the steps below:
- Modify the
Roles
navigation property on yourIdentityUser
entity as follows:
public virtual ICollection<UserRole> Roles { get; set; } = new List<UserRole>();
- Create the
UserRole
entity referenced above:
public class UserRole : IdentityUserRole<int>
{
public virtual IdentityRole<int> Role { get; set; }
}
- Construct the mapping for
UserRole
as follows:
builder.Entity<UserRole>()
.HasOne(e => e.Role)
.WithMany()
.HasForeignKey(e => e.RoleId)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
- You can then retrieve entities (with the navigation property populated) as follows:
User user = context.Set<User>()
.Include(u => u.Roles)
.ThenInclude(r => r.Role)
.FirstOrDefault();
Note:
- As this is loading another side of a many-to-many relatiohship, this may result in more than one call to the database (see N+1 problem).
- As you are creating a new entity that inherits from
IdentityUserRole
you will need to migrate or re-create the database. - If you want to use this navigation property with
UserManager
orRoleManager
you will need to use the long-form overload ofAddUserStore()
andAddRoleStore
in your startup class, e.g.
services.AddIdentity<User, IdentityRole<int>>()
.AddUserStore<UserStore<User, IdentityRole<int>, SqlContext, int, IdentityUserClaim<int>, UserRole, IdentityUserLogin<int>, IdentityUserToken<int>, IdentityRoleClaim<int>>>()
.AddRoleStore<RoleStore<IdentityRole<int>, SqlContext, int, UserRole, IdentityRoleClaim<int>>>()
.AddDefaultTokenProviders();
I fetch roles by custom query and might be helpful.
var roles = (from role in _dbContext.Roles
let userRoles = _dbContext.UserRoles.Where(ur => ur.UserId == user.Id).Select(ur => ur.RoleId)
where userRoles.Contains(role.Id)
select role
).ToList();