Scenario:
Problem:
When I try to add a User with the Roles above, I get this error:
"The instance of entity type 'UserRole' cannot be tracked because another instance with the same key value for {'UserId', 'RoleId'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached."
It seems to be complaining that the PK is UserId and RoleId. But I've already overridden that in my IEntityTypeConfiguration below:
public class UserRoleConfig : IEntityTypeConfiguration<UserRole>
{
public void Configure(EntityTypeBuilder<UserRole> builder)
{
builder.HasKey(r => r.Id);
}
}
ID is a long I've defined below:
public class UserRole : IdentityUserRole<string>
{
public long Id { get; set; }
public int CompanyId { get; set; }
public Company Company { get; set; }
}
My OnModelCreating looks like below.
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ApplyConfiguration(new UserRoleConfig());
}
What am I doing wrong? Why isn't EF respecting my ID override?
How is your store/db context configured, what's your AddIdentity full call look like?
Thanks for the quick response @HaoK. Here it is.
services.AddIdentity<User, Role>(options =>
{
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = true;
options.SignIn.RequireConfirmedEmail = true;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
options.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
Can you include your ApplicationDbContext?
public class ApplicationDbContext : IdentityDbContext<User, Role, string, UserClaim, UserRole, UserLogin, RoleClaim, UserToken>
{
// Other non-Identity DbSets ommitted
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ApplyConfiguration(new RoleConfig());
builder.ApplyConfiguration(new UserConfig());
builder.ApplyConfiguration(new UserRoleConfig());
builder.ApplyConfiguration(new RoleClaimConfig());
builder.ApplyConfiguration(new UserClaimConfig());
builder.ApplyConfiguration(new UserLoginConfig());
builder.ApplyConfiguration(new UserTokenConfig());
// Other config of other entities here
}
}
Strange, everything looks reasonable, can you try getting rid of the base configure and just configuring everything explicitly yourself to see if that at least works?
:( I was hoping it was something dumb I did. (Edit: I should add... still could be something dumb I did. :))
I'm on Central Time so I'm about to head home. Will try it late tonight or tomorrow morning and will report back. Thanks for being so responsive!
If the OnConfiguring doesn't work, I can make you a minimal repro if it would help.
Sure a repro never hurts :)
removing base.OnConfiguring(optionsBuilder); did not help. Will work on a repro.
If I don't inherit from IdentityDbContext and add the config it's adding myself, am I in for a world of hurt in some spot? Or should I be ok?
Not really its just sugar, once you have a repro up hopefully its something simple that's off there.
There's not too much going on other than defining the db sets in the model, and the OnModelCreating:
https://github.com/aspnet/Identity/blob/dev/src/EF/IdentityUserContext.cs
Perfect. I had a once over the code, but just making sure I wasn't missing anything that was looking specifically for something inheriting from IdentityDbContext. Glad to hear there's not much there.
Repro here: https://github.com/scottsauber/AspNetCoreIdentityUserRoleRepro
Repro steps:
So I debugged this, and it looks like the issue is that calling HasKey again doesn't remove the keys added by IdentityDbContext. So the ApplicationUserRole has 2 keys defined:
the composite userId + roleId, in addition to the new long id property that you added.
@ajcvickers is there a way to remove/replace a primary key in the builder?
Specifically
```C#
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// results in: .HasKey(r => new { r.UserId, r.RoleId });
builder.ApplyConfiguration(new ApplicationUserRoleConfiguration());
// results in: .HasKey(r => r.Id);
// Resulting in two keys, is there a way to only have the r.Id key.
}
```
@HaoK - circling back to this... so when I just stop inheriting from IdentityDbContext and add the EF configuration myself... when PasswordSignInAsync is called I get this:
Cannot create a DbSet for 'IdentityUserRole<string>' because this type is not included in the model for the context.
It appears that my custom UserRole class is not being respected.... any ideas?
Edit: Stack Trace....
Microsoft.EntityFrameworkCore.Internal.InternalDbSet.get_EntityType()
Microsoft.EntityFrameworkCore.Internal.InternalDbSet.get_EntityQueryable()
Microsoft.EntityFrameworkCore.Internal.InternalDbSet.System.Linq.IQueryable.get_Provider()
System.Linq.Queryable.Join<TOuter, TInner, TKey, TResult>(IQueryable<TOuter> outer, IEnumerable<TInner> inner, Expression<Func<TOuter, TKey>> outerKeySelector, Expression<Func<TInner, TKey>> innerKeySelector, Expression<Func<TOuter, TInner, TResult>> resultSelector)
Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore+<GetRolesAsync>d__36.MoveNext()
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
System.Runtime.CompilerServices.TaskAwaiter.GetResult()
Microsoft.AspNetCore.Identity.UserManager+<GetRolesAsync>d__110.MoveNext()
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
System.Runtime.CompilerServices.TaskAwaiter.GetResult()
Microsoft.AspNetCore.Identity.UserClaimsPrincipalFactory+<GenerateClaimsAsync>d__5.MoveNext()
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
System.Runtime.CompilerServices.TaskAwaiter.GetResult()
Microsoft.AspNetCore.Identity.UserClaimsPrincipalFactory+<CreateAsync>d__9.MoveNext()
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
System.Runtime.CompilerServices.TaskAwaiter.GetResult()
Microsoft.AspNetCore.Identity.SignInManager+<CreateUserPrincipalAsync>d__25.MoveNext()
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
System.Runtime.CompilerServices.TaskAwaiter.GetResult()
Microsoft.AspNetCore.Identity.SignInManager+<SignInAsync>d__30.MoveNext()
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
Microsoft.AspNetCore.Identity.SignInManager+<SignInOrTwoFactorAsync>d__52.MoveNext()
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
System.Runtime.CompilerServices.TaskAwaiter.GetResult()
Microsoft.AspNetCore.Identity.SignInManager+<PasswordSignInAsync>d__33.MoveNext()
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
System.Runtime.CompilerServices.TaskAwaiter.GetResult()
Microsoft.AspNetCore.Identity.SignInManager+<PasswordSignInAsync>d__34.MoveNext()
@HaoK - never mind, I got around this by still inheriting from IdentityDbContext but just not calling base.OnModelCreating
@scottsauber Sorry for being late to the conversation. I suspect this was happening because the original key was still being referenced by an FK. In general, when changing the primary key of an entity type any FKs that reference that primary must also be changed, otherwise the original key is still needed as well.
There is now some better documentation on customizing the Identity model: https://docs.microsoft.com/en-us/aspnet/core/security/authentication/customize_identity_model?view=aspnetcore-2.1