I'm trying to update the users using UserManager<TUser> but getting the following error
The instance of entity type 'UserEntity' cannot be tracked because another instance with the key value '{Id}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
There are several issues in the internet with respect to above error, but none is while updating the user via UserManager.
Here is my code:
Services Configuration:
services.AddScoped<IUserRepository, UserRepository>();
services.AddIdentity<UserEntity, UserRoleEntity>()
.AddEntityFrameworkStores<GallaContext>()
.AddDefaultTokenProviders();
UserEntity:
public class UserEntity : IdentityUser<Guid>
{
private ProfileEntity _profile;
private UserEntity(ILazyLoader lazyLoader)
{
LazyLoader = lazyLoader;
}
private ILazyLoader LazyLoader { get; set; }
public UserEntity()
{
}
public string FirstName { get; set; }
public string LastName { get; set; }
public string Designation { get; set; }
public string IdentityNumber { get; set; }
public bool Active { get; set; }
public DateTimeOffset CreatedAt { get; set; }
public string CreatedBy { get; set; }
public DateTimeOffset? ModifiedAt { get; set; }
public string ModifiedBy { get; set; }
public Guid ProfileId { get; set; }
public ProfileEntity Profile { get => LazyLoader.Load(this, ref _profile); set => _profile = value; }
}
Razor Page Handler:
public async Task<IActionResult> OnPutAsync(
[FromForm] UserViewModel userViewModel,
CancellationToken ct)
{
try
{
await _user.InitializeAsync(userViewModel.Id, ct);
var user = _mapper.Map(userViewModel, _user);
var result = await user.SaveAsync(ct);
return new JsonResult(new AjaxResultHelper<string>(result.Succeeded)
{
Response = result.ErrorMessage ?? ResponseMessages.DataSaved
});
}
catch (Exception e)
{
// Need to log the error
return new JsonResult(new AjaxResultHelper<string>()
{
Response = e.Message
});
}
}
UserBusiness:
public class User : CoreEntityBase
{
private readonly IUserRepository _userRepository;
private readonly IMapper _mapper;
public User(
IUserRepository userRepository,
IMapper mapper)
{
_userRepository = userRepository;
_mapper = mapper;
}
public string Name => $"{FirstName} {LastName}";
public string UserName { get; private set; }
public string NormalizedUserName { get; private set; }
public string Email { get; private set; }
public string NormalizedEmail { get; private set; }
public bool EmailConfirmed { get; private set; }
public string PasswordHash { get; private set; }
public string SecurityStamp { get; private set; }
public string ConcurrencyStamp { get; private set; }
public bool LockoutEnabled { get; private set; }
[Sortable(Default = true)]
[SearchableString]
public string FirstName { get; set; }
[Sortable]
[SearchableString]
public string LastName { get; set; }
[Sortable]
[SearchableString]
public string Designation { get; set; }
[Sortable]
[SearchableString]
public string IdentityNumber { get; set; }
public Guid ProfileId { get; set; }
[NestedSortable]
[NestedSearchable]
public Profile Profile { get; set; }
public async Task InitializeAsync(
Guid Id,
CancellationToken ct)
{
if (Id == null)
{
throw new ArgumentNullException($"{nameof(Id)} cannot be null");
}
var user = await _userRepository.GetUserByIdAsync(Id, ct);
_mapper.Map(user, this);
SecurityStamp = user.SecurityStamp;
UserName = user.UserName;
}
public async Task<User> GetUserByIdAsync(
Guid userId,
CancellationToken ct)
{
var user = await _userRepository.GetUserByIdAsync(userId, ct);
return _mapper.Map<User>(user);
}
public async Task<(bool Succeeded, string ErrorMessage)> SaveAsync(CancellationToken ct)
{
if (!Validate())
{
return (false, Errors?.First());
}
//var userEntity = await _userRepository.GetUserByIdAsync(Id, ct);
//var userEntity = _mapper.Map(this, userEntity);
var update = await _userRepository.UpdateUserAsync(this);
return update;
}
public override bool Validate()
{
var isValid = true;
if (string.IsNullOrWhiteSpace(FirstName))
{
Errors.Add("FirstName cannot be empty");
isValid = false;
}
if (string.IsNullOrWhiteSpace(LastName))
{
Errors.Add("LastName cannot be empty");
isValid = false;
}
if (string.IsNullOrWhiteSpace(Designation))
{
Errors.Add("Designation cannot be empty");
isValid = false;
}
return isValid;
}
}
UserRepository:
public class UserRepository : IUserRepository
{
private readonly UserManager<UserEntity> _userManager;
private readonly IMapper _mapper;
public UserRepository(
UserManager<UserEntity> userManager,
IMapper mapper)
{
_userManager = userManager;
_mapper = mapper;
}
public async Task<UserEntity> GetUserByIdAsync(
Guid userId,
CancellationToken ct)
{
var entity = await _userManager.Users.AsNoTracking().TagWith(nameof(GetUserIdAsync))
.Include(u => u.Profile)
.SingleOrDefaultAsync(x => x.Id == userId, ct);
return entity;
}
public async Task<(bool Succeeded, string ErrorMessage)> UpdateUserAsync(User user)
{
var userEntity = _mapper.Map<UserEntity>(user);
var result = await _userManager.UpdateAsync(userEntity);
if (result.Succeeded)
{
return (true, null);
}
var firstError = result.Errors.FirstOrDefault()?.Description;
return (false, firstError);
}
}
I'm not able to figure out where I'm wrong. I have AsNoTracking() set for _userManager.Users in GetUserByIdAsync() method; but still its not working and keep throwing the same error. Am I doing in a wrong way? Please assist on what needs to be corrected and where i'm going wrong.
Thanks in advance.
After some analysis figured out the issue. This is because of GetUserByIdAsync(). Though I set AsNoTracking() to _userManager.Users its being tracked by ef core internally. So changed the implementation to return user by _userManager.FindByIdAsync and that worked.
Here is the final cleaned code. Hope this helps someone.
Razor Page Handler:
public async Task<IActionResult> OnPutAsync([FromForm] UserViewModel userViewModel)
{
try
{
await _user.InitializeAsync(userViewModel.Id);
var user = _mapper.Map(userViewModel, _user);
var (Succeeded, ErrorMessage) = await user.SaveAsync();
return new JsonResult(new AjaxResultHelper<string>(Succeeded)
{
Response = ErrorMessage ?? ResponseMessages.DataSaved
});
}
catch (Exception ex)
{
// Need to log the error
Console.WriteLine(ex.Message);
return new JsonResult(new AjaxResultHelper<string>()
{
Response = ResponseMessages.DataNotSaved
});
}
}
User Business:
public class User : CoreEntityBase
{
private readonly IUserRepository _userRepository;
private readonly IMapper _mapper;
public User()
{
}
public User(
IUserRepository userRepository,
IMapper mapper)
{
_userRepository = userRepository;
_mapper = mapper;
}
public string Name { get; set; }
[Sortable(Default = true)]
[SearchableString]
public string FirstName { get; set; }
[Sortable]
[SearchableString]
public string LastName { get; set; }
[Sortable]
[SearchableString]
public string Designation { get; set; }
[Sortable]
[SearchableString]
public string IdentityNumber { get; set; }
public Guid ProfileId { get; set; }
[NestedSortable]
[NestedSearchable]
public Profile Profile { get; set; }
public async Task InitializeAsync(Guid Id)
{
if (Id == null)
{
throw new ArgumentNullException($"{nameof(Id)} cannot be null");
}
var userEntity = await _userRepository.GetUserByIdAsync(Id);
_mapper.Map(userEntity, this);
}
public async Task<PagedResults<User>> GetUsersAsync(
PagingOptions pagingOptions,
SortOptions<User, UserEntity> sortOptions,
SearchOptions<User, UserEntity> searchOptions,
CancellationToken ct)
{
var users = await _userRepository.GetUsersAsync(pagingOptions, sortOptions, searchOptions, ct);
return new PagedResults<User>
{
Items = _mapper.Map<User[]>(users.Items),
TotalSize = users.TotalSize
};
}
public async Task<User> GetUserByIdAsync(Guid userId)
{
var userEntity = await _userRepository.GetUserByIdAsync(userId);
return _mapper.Map<User>(userEntity);
}
public async Task<(bool Succeeded, string ErrorMessage)> SaveAsync()
{
if (!Validate())
{
return (false, Error);
}
var userEntity = await _userRepository.GetUserByIdAsync(Id);
userEntity = _mapper.Map(this, userEntity);
var update = await _userRepository.UpdateUserAsync(userEntity);
return update;
}
public override bool Validate()
{
var isValid = true;
if (string.IsNullOrWhiteSpace(FirstName))
{
Error = "FirstName cannot be empty";
isValid = false;
}
if (string.IsNullOrWhiteSpace(LastName))
{
Error = "LastName cannot be empty";
isValid = false;
}
if (string.IsNullOrWhiteSpace(Designation))
{
Error = "Designation cannot be empty";
isValid = false;
}
return isValid;
}
}
User Repository:
public class UserRepository : IUserRepository
{
private readonly UserManager<UserEntity> _userManager;
public UserRepository(
UserManager<UserEntity> userManager)
{
_userManager = userManager;
}
public async Task<UserEntity> GetUserByIdAsync(Guid userId)
{
var entity = await _userManager.FindByIdAsync(userId.ToString());
return entity;
}
public async Task<(bool Succeeded, string ErrorMessage)> UpdateUserAsync(UserEntity userEntity)
{
var result = await _userManager.UpdateAsync(userEntity);
if (result.Succeeded)
{
return (true, null);
}
var firstError = result.Errors.FirstOrDefault()?.Description;
return (false, firstError);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With