EF中重复条目校验与“实体已被跟踪”错误解决求助
解决EF编辑时实体跟踪冲突+保留重复校验的方案
错误原因
GET请求中获取的editedCustomer已经被EF上下文跟踪,POST里的editedCustomerVM.Customer是表单绑定生成的新实例。调用Update时,EF发现同一个CustomerID对应两个被跟踪的实体实例,因此抛出冲突错误。
可行解决方案
方案1:复用已跟踪实例(推荐)
思路:直接使用GET时已被跟踪的实体,将表单提交的属性值赋值给它,EF会自动检测属性变化并提交更新;同时给校验查询加上AsNoTracking,避免额外跟踪实体。
修改POST方法代码:
[HttpPost] public IActionResult Edit(CustomerViewModel editedCustomerVM) { // 校验重复时添加AsNoTracking,避免查询结果被EF跟踪 var result = _unitOfWork.Customer.GetAll() .AsNoTracking() .Where(c => c.CustomerID != editedCustomerVM.Customer.CustomerID) .Where(c => c.CustomerEmail == editedCustomerVM.Customer.CustomerEmail) .Any(); if (result) { var objVM = ReCreateGetView(editedCustomerVM.Customer); CreateCustomerAlert(objVM, "Possible duplicated entry existing!"); return View(objVM); } // 获取上下文已跟踪的原实体 var trackedCustomer = _unitOfWork.Customer.Get(c => c.CustomerID == editedCustomerVM.Customer.CustomerID); // 将表单提交的属性值赋值给原实体(可封装成通用更新方法) trackedCustomer.CustomerEmail = editedCustomerVM.Customer.CustomerEmail; trackedCustomer.Name = editedCustomerVM.Customer.Name; // ...其他需要更新的属性 _unitOfWork.Save(); // EF自动检测变化并提交 // ...后续跳转或逻辑 }
方案2:解除原有实例跟踪后更新新实例
思路:如果一定要使用表单绑定的新实例,先解除上下文中原跟踪实例的跟踪状态,再提交新实例的更新。
修改POST方法代码:
[HttpPost] public IActionResult Edit(CustomerViewModel editedCustomerVM) { var result = _unitOfWork.Customer.GetAll() .AsNoTracking() .Where(c => c.CustomerID != editedCustomerVM.Customer.CustomerID) .Where(c => c.CustomerEmail == editedCustomerVM.Customer.CustomerEmail) .Any(); if (result) { var objVM = ReCreateGetView(editedCustomerVM.Customer); CreateCustomerAlert(objVM, "Possible duplicated entry existing!"); return View(objVM); } // 获取原跟踪实例并解除跟踪 var existingCustomer = _unitOfWork.Customer.Get(c => c.CustomerID == editedCustomerVM.Customer.CustomerID); _unitOfWork.Context.Entry(existingCustomer).State = EntityState.Detached; // 现在可以正常更新新实例 _unitOfWork.Customer.Update(editedCustomerVM.Customer); _unitOfWork.Save(); // ...后续逻辑 }
注:需要确保你的UnitOfWork能直接访问DbContext(即_unitOfWork.Context)。
方案3:使用TryUpdateModelAsync绑定已跟踪实例
思路:在POST中直接获取已跟踪实体,用MVC内置的TryUpdateModelAsync将表单数据绑定到该实体,全程复用同一个跟踪实例。
修改POST方法代码:
[HttpPost] public async Task<IActionResult> Edit(int id, CustomerViewModel editedCustomerVM) { var trackedCustomer = _unitOfWork.Customer.Get(c => c.CustomerID == id); if (trackedCustomer == null) { return NotFound(); } // 校验用户输入的新邮箱是否重复 string newEmail = editedCustomerVM.Customer.CustomerEmail; var result = _unitOfWork.Customer.GetAll() .AsNoTracking() .Where(c => c.CustomerID != id) .Where(c => c.CustomerEmail == newEmail) .Any(); if (result) { var objVM = ReCreateGetView(trackedCustomer); CreateCustomerAlert(objVM, "Possible duplicated entry existing!"); return View(objVM); } // 将表单数据绑定到已跟踪实体 if (await TryUpdateModelAsync(trackedCustomer, "Customer", c => c.CustomerEmail, c => c.Name /* 列出需要更新的属性 */)) { _unitOfWork.Save(); return RedirectToAction(nameof(Index)); } return View(ReCreateGetView(trackedCustomer)); }
关键注意点
- 所有校验重复的查询必须加上
AsNoTracking,防止EF跟踪查询结果引发额外冲突; - 优先复用已跟踪实体,这是EF开发的常规做法,能避免大部分跟踪冲突问题。
内容的提问来源于stack exchange,提问作者kkkristo
相关产品推荐
相关产品推荐

