尝试更新具有导航属性的实体会引发DbConcurrencyException

时间:2019-05-26 21:21:51

标签: .net-core entity-framework-core repository-pattern cqrs mediatr

我正在沿实体框架学习ASP.NET Core(v 2.2)。我已经使用CQRS和存储库模式以及MediatR和Auto-Mapper作为实用程序进行开发和应用。

我正在使用Identity来管理我的应用程序用户,因此我创建了一个User类,该类继承了IdentityUser并向身份添加了一些额外的字段,例如名字和姓氏。此外,主要应用程序用户扩展了User并分别添加了几个特定于用户类型的额外字段。

我似乎无法更新用户的任何信息,因为EF似乎已经在跟踪该实体。我仅更新某些字段(例如名字或姓氏)。我还在调试中注意到EF对所有相关实体执行了级联更新。让我们将用户视为Medic。该用户具有诊断程序的链接(MedicDiagnostic链接表,其中包含用于医生和诊断程序的外键)。更新名字也会在所有诊断程序中更新。在存储库中执行SaveChangesAsync时会引发异常。

Exception has occurred: CLR/System.InvalidOperationException Exception thrown: 'System.InvalidOperationException' in System.Private.CoreLib.dll: 'The instance of entity type 'Medic' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using

完整日志here

我应该如何处理?我曾尝试创建一个通用的UserRepository,但是这失败了,因为我找不到动态加载相关数据的方法,所以我创建了一个Frankenstein,其中既包含用于检索医疗人员的上下文,又包含相关数据,但是仍使用UserRepository。有更好的方法来解决这个问题吗?

完整的存储库可用here

现在,对于某些代码段:

  • Medic控制器中的GET / POST编辑方法:

        // GET: Medic/Edit/id
        [Authorize (Roles = "Receptionist,Admin,Medic")]
        public async Task<IActionResult> Edit (string id) {
            if (id == null) {
                return NotFound ();
            }
    
            var medicEditModel = await _mediator.Send (new GetMedicByIdForEditQuery (id));
    
            if (medicEditModel == null) {
                return NotFound ();
            }
    
            return View (medicEditModel);
        }
    
        // POST: Medic/Edit/id
        [HttpPost]
        [ValidateAntiForgeryToken]
        [Authorize (Roles = "Receptionist,Admin,Medic")]
        public async Task<IActionResult> Edit (string id, [Bind ("Medic")] MedicEditModel medicEditModel) {
            if (medicEditModel.ModelState.IsValid) {
                var editSucceeded = await _mediator.Send (new EditMedicCommand (id, medicEditModel.Medic));
                if (editSucceeded) {
                    return RedirectToAction ("Details", "Medic", new { id = id });
                }
            }
            return View ();
        }
    
  • 编辑命令和处理程序:

    public class EditMedicCommand : IRequest<bool>
    {
        public EditMedicCommand(string medicId, MedicEditDTO medic)
        {
            Medic = medic;
            MedicId = medicId;
        }
    
        public string MedicId { get; set; }
        public MedicEditDTO Medic { get; set; }
    }
    
    public class EditMedicCommandHandler : IRequestHandler<EditMedicCommand, bool>
    {
        private readonly IMedicRepository _medicRepository;
        private readonly ILogger<EditMedicCommandHandler> _logger;
        private readonly IMapper _mapper;
    
        public EditMedicCommandHandler(IMedicRepository medicRepository,
            ILogger<EditMedicCommandHandler> logger,
            IMapper mapper)
        {
            _medicRepository = medicRepository;
            _logger = logger;
            _mapper = mapper;
        }
    
        public async Task<bool> Handle(EditMedicCommand request, CancellationToken cancellationToken)
        {
            Medic existingMedic = await _medicRepository.GetMedicByID(request.MedicId);
    
            if (existingMedic == null)
            {
                var errorMessage = $"No medic found for id {request.MedicId}";
                _logger.LogError(errorMessage);
                return false;
            }
    
            existingMedic.rank = request.Medic.rank;
            existingMedic.firstName = request.Medic.firstName;
            existingMedic.lastName = request.Medic.lastName;
            existingMedic.personalNumericalCode = request.Medic.personalNumericalCode;
            existingMedic.gender = request.Medic.gender;
            existingMedic.Email = request.Medic.Email;
            existingMedic.UserName = request.Medic.Email;
            existingMedic.address = request.Medic.address;
    
    
            try
            {
                await _medicRepository.UpdateMedic(existingMedic, request.Medic.Password);
            }
            catch (Exception)
            {
                return false;
            }
            return true;
        }
    }
    
  • Medic存储库:

    public class MedicRepository : IMedicRepository
    {
        private readonly IUserRepository<Medic> _userRepository;
        private readonly IDbContext _context;
        private readonly ILogger<MedicRepository> _logger;
        private const string _role = "Medic";
    
        public MedicRepository(
            IUserRepository<Medic> userRepository,
            ILogger<MedicRepository> logger,
            IDbContext context)
        {
            _userRepository = userRepository;
            _logger = logger;
            _context = context;
        }
    
        public async Task AddMedic(Medic medic, string Password)
        {
            await _userRepository.Add(medic, Password, _role);
        }
    
        public async Task UpdateMedic(Medic medic, string NewPassword)
        {
            if (!String.IsNullOrEmpty(NewPassword))
            {
                medic.PasswordHash = _userRepository.UpdatePassword(medic, NewPassword);
    
            }
            await _userRepository.Update(medic);
        }
    
        public async Task<Medic> GetMedicByID(string id)
        {
            return await _context.Medics
            .Include(medic => medic.Diagnostics).ThenInclude(diagnostic => diagnostic.Diagnostic)
                .ThenInclude(medication => medication.Medications)
                .ThenInclude(medication => medication.Medication)
                .ThenInclude(medicine => medicine.Medicines)
            .Include(medic => medic.Departments).ThenInclude(department => department.Department)
            .FirstOrDefaultAsync(medic => medic.Id == id);
        }
    
        public async Task<IEnumerable<Medic>> GetAllMedics()
        {
            return await _userRepository.GetAll(_role);
        }
    
        public async Task<bool> AnyMedic(Func<Medic, bool> predicate)
        {
            return await _userRepository.Any(predicate, _role);
        }
    
        public async Task RemoveMedic(Medic medic)
        {
            await _userRepository.Remove(medic);
        }
    }
    
  • UserRepository

    public class UserRepository<TEntity> : IUserRepository<TEntity> where TEntity : User {
        private readonly UserManager<TEntity> _userManager;
        private readonly ILogger<UserRepository<TEntity>> _logger;
    
        public UserRepository (
            ApplicationDbContext context,
            IServiceProvider serviceProvider,
            ILogger<UserRepository<TEntity>> logger
        ) {
            _userManager = serviceProvider.GetRequiredService<UserManager<TEntity>> ();
            _logger = logger;
        }
    
        public async Task Add (TEntity user, string password, string role) {
            if (user != null) {
                var userCreation = await _userManager.CreateAsync (user, password);
    
                if (userCreation.Succeeded) {
                    await _userManager.AddToRoleAsync (user, role);
                } else {
                    var errorMessage = $"Failed to create user {user.ToString()}.Aborting creation!";
                    _logger.LogError (errorMessage);
                    throw new TaskFailedException (errorMessage);
                }
            } else {
                var errorMessage = "Null user! Aborting creation!";
                _logger.LogError (errorMessage);
                throw new InvalidArgumentException (errorMessage);
            }
        }
    
        public async Task Remove (TEntity user) {
            if (user != null) {
                var userDeleteion = await _userManager.DeleteAsync (user);
                if (!userDeleteion.Succeeded) {
                    var errorMessage = $"Failed to remove user {user.ToString()}.Aborting removal!";
                    _logger.LogError (errorMessage);
                    throw new TaskFailedException (errorMessage);
                }
            } else {
                var errorMessage = "Null user! Aborting creation!";
                _logger.LogError (errorMessage);
                throw new InvalidArgumentException (errorMessage);
            }
        }
    
        public async Task Update (TEntity user) {
            if (user != null) {
                var userUpdate = await _userManager.UpdateAsync (user);
                if (!userUpdate.Succeeded) {
                    var errorMessage = $"Failed to update user {user.ToString()}.Aborting update!";
                    _logger.LogError (errorMessage);
                    throw new TaskFailedException (errorMessage);
                }
            } else {
                var errorMessage = "Null user! Aborting creation!";
                _logger.LogError (errorMessage);
                throw new InvalidArgumentException (errorMessage);
            }
        }
    
        public async Task<IEnumerable<TEntity>> GetAll (string role) {
            return await _userManager.GetUsersInRoleAsync (role);
        }
    
        public async Task<TEntity> GetByEmail (string email) {
            return await getUserByEmail (email).ToAsyncEnumerable ().FirstOrDefault ();
        }
    
        public async Task<bool> Any (Func<TEntity, bool> predicate, string role) {
            IEnumerable<TEntity> usersWithRole = await GetAll (role);
            return usersWithRole.Any (predicate);
        }
    
        private async Task<TEntity> getUserByEmail (string email) {
            TEntity user = null;
            if (email != null) {
                user = await _userManager.FindByEmailAsync (email);
            } else {
                var errorMessage = "Null email address provided! Aborting search by email!";
                _logger.LogError (errorMessage);
                throw new InvalidArgumentException (errorMessage);
            }
            return user;
        }
    
        public async Task<TEntity> GetById (string id) {
            TEntity user = null;
            if (id != null) {
                user = await _userManager.FindByIdAsync (id);
            } else {
                var errorMessage = "Null id provided! Aborting search by id!";
                _logger.LogError (errorMessage);
                throw new InvalidArgumentException (errorMessage);
            }
            return user;
        }
    
        public string UpdatePassword (TEntity user, string password) {
            return _userManager.PasswordHasher.HashPassword (user, password);
        }
    }
    

谢谢!

0 个答案:

没有答案