简体   繁体   中英

unable to update user via UserManager<TUser> using ef core 2.2 in asp.net core 2.2 app

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);
        }
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM