Reputation: 9485
I am setting up a new project using EF Core 2, and I need to have a navigation property on the IdentityUser so when I query for a user I can include(x => x.Roles) and get the Roles the user is in.
This post on Github has some ideas, but I have tried each one and all cause issues, by creating new/duplicate fields on the Identity tables or cause issues with migrations. And no official comment from anyone in the EF team.
https://github.com/aspnet/Identity/issues/1361
I was wondering if anyone has this working correctly? And could share their EF DB mappings and models.
Upvotes: 15
Views: 11169
Reputation: 11564
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();
Upvotes: 6
Reputation: 318
Adding some more comments to accepted answer and to comment Correct me if I wrong but in above case we already have all tables in DB and all relations are there, so we just need to reflect that in our DbContext and we DON'T need migrations for that.
Here what I did:
My TKey is Guid, so I changed my DbContext to look like this:
public class ApplicationDbContext : IdentityDbContext<User, IdentityRole<Guid>, Guid, IdentityUserClaim<Guid>, UserRole, IdentityUserLogin<Guid>, IdentityRoleClaim<Guid>, IdentityUserToken<Guid>>
as it was suggested by Prince Owen
Changed mapping:
modelBuilder.Entity<UserRole>()
.HasOne<User>()
.WithMany(u => u.UserRoles)
.HasForeignKey(x => x.UserId).IsRequired()
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<UserRole>()
.HasOne(e => e.Role)
.WithMany()
.HasForeignKey(e => e.RoleId)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
Then you can access user roles easily by:
user.Roles.Select(r => r.Name).ToList()
to get all roles for user.
No migration is needed.
Upvotes: 0
Reputation: 1515
Adding my 10 cents to the already brilliant accepted answer. It is important to remember to adapt your DbContext
to the changes you will be making.
In the case of the accepted answer, you DbContext
should look similar to this:
public class DbContext: IdentityDbContext<User, IdentityRole<int>, string,
IdentityUserClaim<string>, UserRole, IdentityUserLogin<string>,
IdentityRoleClaim<string>, IdentityUserToken<string>>,
IDesignTimeDbContextFactory<ApplicationContext>
{
// ...Rest of the code goes here
}
Notice that UserRole
replaces the default TUserRole
parameter. Took me a couple of hours to figure this out, I hope it saves some else's time.
Upvotes: 2
Reputation: 10320
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);
}
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:
Roles
navigation property on your IdentityUser
entity as follows:public virtual ICollection<UserRole> Roles { get; set; } = new List<UserRole>();
UserRole
entity referenced above:public class UserRole : IdentityUserRole<int>
{
public virtual IdentityRole<int> Role { get; set; }
}
UserRole
as follows:builder.Entity<UserRole>()
.HasOne(e => e.Role)
.WithMany()
.HasForeignKey(e => e.RoleId)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
User user = context.Set<User>()
.Include(u => u.Roles)
.ThenInclude(r => r.Role)
.FirstOrDefault();
Note:
IdentityUserRole
you will need to migrate or re-create the database.UserManager
or
RoleManager
you will need to use the long-form overload of
AddUserStore()
and AddRoleStore
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();
Upvotes: 25