Using UpdateAsync method of UserManager throws InvalidOperationException on EntityFrameworkCore

113 views Asked by At

I am in the process of updating a dotnet Framework project to dotnet Core. I have a UserProfileService class to handle all the users. This had a UserManager which is injected using the dotnet core dependency injection.

When I try to update a UserProfile (which is a class that is derived from IdentityUser) is throws the following InvalidOperationException error:

The instance of entity type 'UserProfile' cannot be tracked because another instance with the key value '{Id: GUID}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.

The dbContext is connected using the following code in Startup.cs:

services.AddIdentityCore<UserProfile>(options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddRoleManager<RoleManager<IdentityRole>>()
.AddEntityFrameworkStores<AvgContext>()
.AddDefaultTokenProviders();

I am not getting this user from the context before this call, so it seems very unlikely that this would happen.

Does anybody know what could be the case?

1

There are 1 answers

0
Steve Py On

I am not getting this user from the context before this call

Directly, maybe not. Indirectly, this still counts. If you are using a DbContext to load the user entity you should always verify whether an entity is tracked before using Update or Attach:

var trackedUser = _dbContext.Users.Local.FirstOrDefault(x => x.UserId == userId);

if trackedUser comes back with a value you should copy the data across to it and call SaveChanges, otherwise you can consider attaching or using Update, though I generally recommend always loading the entity in an update scenario as this asserts the entity exists, and you can check any current concurrency flags (I.e. RowVersion/Timestamp) before apply a possibly stale update.

As a simple rule, avoid using Update/UpdateAsync. when working with DbContext. UserManager offers a bit of a wrapper around EF, but still aim to copy values across.

var user = await UserManager.FindByIdAsync(userId);
// Copy values across based on what is allowed to be updated.
user.Email = dto.Email; // etc.
await UserManager.UpdateAsync(user);

If you are using a DbContext directly to access a User entity:

var user = await _dbContext.Users.SingleAsync(x => x.UserId == userId);
// copy values across based on what is allowed to be updated.
user.Email = dto.Email; // etc.
await _dbContext.SaveChangesAsync();