Update:
When upgrading NuGet Duende.IdentityServer.EntityFramework.Storage
to 6.1.0 I got the following error:
CS0535 'ApplicationApiAuthorizationDbContext<TUser, TRole>' does not
implement interface member
'IPersistedGrantDbContext.ServerSideSessions'
ApplicationApiAuthorizationDbContext.cs
now needs another DbSet
like this:
public DbSet<ServerSideSession> ServerSideSessions
{
get;
set;
}
This caused the error below though for endpoints.MapRazorPages();
.
System.Reflection.ReflectionTypeLoadException: 'Unable to load one or
more of the requested types. Method 'get_ServerSideSessions' in type
'Microsoft.AspNetCore.ApiAuthorization.IdentityServer.ApiAuthorizationDbContext`1'
from assembly 'Microsoft.AspNetCore.ApiAuthorization.IdentityServer,
Version=6.0.5.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'
does not have an implementation.'
Recommend staying on Duende.IdentityServer.EntityFramework.Storage
5.2.0
until this is fixed.
Original:
As @Dreamescaper and @graycrow says you could use shadow many-to-many navigation in EF Core 5.0 even though it should not work.
https://github.com/dotnet/efcore/issues/25383#issuecomment-894785144
https://github.com/dotnet/efcore/issues/23362
Support might be added in EF Core 7.0 with unidirectional many-to-many relationships through shadow navigations again but not completed yet:
https://github.com/dotnet/efcore/issues/3864
I got it working like this using EF Core 6.0:
ApplicationUser:
public class ApplicationUser : IdentityUser
{
public ICollection<ApplicationRole> Roles { get; set; }
}
ApplicationRole:
public class ApplicationRole : IdentityRole
{
public ICollection<ApplicationUser> Users { get; set; }
}
Program.cs
or Startup.cs
:
services.AddDefaultIdentity<ApplicationUser>(options =>
options.SignIn.RequireConfirmedAccount = false)
.AddRoles<ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
ApplicationApiAuthorizationDbContext:
//Based on Microsoft.AspNetCore.ApiAuthorization.IdentityServer.ApiAuthorizationDbContext, Version=6.0.2.0
//https://github.com/dotnet/aspnetcore/issues/14161#issuecomment-533468760
public class ApplicationApiAuthorizationDbContext<TUser, TRole> : IdentityDbContext<TUser, TRole, string>, IPersistedGrantDbContext, IDisposable where TUser : IdentityUser where TRole : IdentityRole
{
private readonly IOptions<OperationalStoreOptions> _operationalStoreOptions;
public DbSet<PersistedGrant> PersistedGrants
{
get;
set;
}
public DbSet<DeviceFlowCodes> DeviceFlowCodes
{
get;
set;
}
public DbSet<Key> Keys
{
get;
set;
}
public ApplicationApiAuthorizationDbContext(DbContextOptions options, IOptions<OperationalStoreOptions> operationalStoreOptions)
: base(options)
{
_operationalStoreOptions = operationalStoreOptions;
}
Task<int> IPersistedGrantDbContext.SaveChangesAsync()
{
return base.SaveChangesAsync();
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value);
}
}
ApplicationDbContext
inherits from ApplicationApiAuthorizationDbContext<ApplicationUser, ApplicationRole>
instead of ApiAuthorizationDbContext<ApplicationUser>
public class ApplicationDbContext : ApplicationApiAuthorizationDbContext<ApplicationUser, ApplicationRole>
modelBuilder.Entity<ApplicationUser>()
.HasMany(u => u.Roles)
.WithMany(r => r.Users)
.UsingEntity<IdentityUserRole<string>>(
userRole => userRole.HasOne<ApplicationRole>()
.WithMany()
.HasForeignKey(ur => ur.RoleId)
.IsRequired(),
userRole => userRole.HasOne<ApplicationUser>()
.WithMany()
.HasForeignKey(ur => ur.UserId)
.IsRequired());
You can then get all users with roles like this:
var usersWithRoles = dbContext.Users.Include(x => x.Roles).ToList();
