EF 中的重复条目验证和“实体已被跟踪”错误

问题描述 投票:0回答:1

请在初学者的道路上帮助我,我正在尝试在我的客户类上设置重复条目验证,过滤“电子邮件”或“姓名”值。 验证过滤器有效,正在保存新条目,但是在编辑时,我无法更新实体并在 db.Update() 上不断收到错误:

System.InvalidOperationException:“无法跟踪实体类型“Customer”的实例,因为已跟踪具有相同键值 {'CustomerID'} 的另一个实例。附加现有实体时,请确保仅附加一个具有给定键值的实体实例。

虽然该消息看起来不言自明,但我不知道要更改什么才能使代码正常工作并保留重复条目验证。

我的行动是:

public IActionResult Edit(int? id)
 {
     var editedCustomer = _unitOfWork.Customer.Get(c => c.CustomerID == id);
     CustomerViewModel customerVM = new()
     {
         Customer = editedCustomer
     };
     return View(customerVM);
 }
[HttpPost]
 public IActionResult Edit(CustomerViewModel editedCustomerVM)
 {            
     var result =  _unitOfWork.Customer.GetAll()
         .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);
     }

     _unitOfWork.Customer.Update(editedCustomerVM.Customer);
   ...
   ...

尝试具体化“结果”及其元素,以各种方式将数据从查询移动到内存,但根本没有帮助。在 Post 方法中删除“_unitOfWork”调用会有所帮助,但它显然也排除了预期的防重复验证。

asp.net-mvc entity-framework asp.net-core validation
1个回答
0
投票

当您尝试在 DbContext 已加载并正在跟踪的实体实例上使用

Update
时,会导致此问题。

 var result =  _unitOfWork.Customer.GetAll()
     .Where(c => c.CustomerID != editedCustomerVM.Customer.CustomerID)
     .Where(c => c.CustomerEmail == editedCustomerVM.Customer.CustomerEmail)
     .Any();

上面的“GetAll()”方法很可能返回所有客户的具体化列表,而不是

IQueryable<Customer>()
。这是昂贵的,并且完全没有必要,因为 EF DbContext 已经作为一个工作单元运行。

作为一般规则,我建议避免在控制器和视图之间传递实体。实体代表数据状态,视图模型代表视图状态。 EditedCustomerViewModel 应包含您允许编辑的值,并且仅包含可以更新的值。接受 Customer 作为视图模型的一部分并调用

Update()
看起来是一种更新数据的简单而方便的方法,但从某种意义上说它是危险的,因为它可能允许您不打算更改的数据被更改,并且它一旦您开始引入导航属性等,也会遇到麻烦。

相反,考虑在视图模型中跟踪并提供修改后的字段,那么更新看起来更像是:

[HttpPost]
 public IActionResult Edit(CustomerViewModel editedCustomerVM)
 {            
     var duplicateEmailAddress = _context.Customers
          .Where(c => c.CustomerID != editedCustomerVm.CustomerID
              && c.EmailAddress == editedCustomerVm.EmailAddress)
          .Any();
     if(duplicateEmailAddress)
     {
         string validationMessage = "Another employee exists for the same Email address.";
         return RedirectToAction("Edit", new {EmployeeID = editedCustomerVM.EmployeeID, validationMessage});
     } 

     var customer =  _context.Customers.Single(c => c.CustomerID == editedCustomerVM.CustomerID);

     // Copy allowed values from the view model into the entity.
     customer.UpdateableValue = editedCustomerVM.UpdatedValue;

     _context.SaveChanges();

     return View(new { CustomerID = editedCustomerVM.CustomerID })
}

此处的主要细节:当前往 DbContext 检查重复的电子邮件地址时,我们希望确保不会将任何实体加载到跟踪缓存中。我怀疑您拥有的 UoW 包装器正在具体化实体,这会带来问题,并且在性能方面造成浪费。

如果我们验证并查找重复的电子邮件地址,则返回 RedirectResponse 并将消息作为该响应的一部分传递。编辑视图可以查找validationMessage并为用户显示诸如Toastr警报或what-have-you之类的内容。现在,在此示例中,所有更改都将被丢弃,因此编辑视图将重新加载所有数据。或者,您可以继续将数据从虚拟机复制到实体(电子邮件地址除外),保存这些更改,然后返回到带有验证警告的编辑页面。

在更新实体时,我们从 DbContext 加载实体,然后在调用 SaveChanges 之前复制允许的值,而不是使用

Update()
方法。
Update()
适用于分离的实体,但前提是您确保 DbContext 尚未跟踪实体。我不建议使用这种方法来更新数据,因为即使在最好的情况下,您确定没有跟踪引用来搞砸事情,
Update
也会为表中的所有字段生成 SQL UPDATE 语句,无论是否更改了任何内容。使用 EF 更改跟踪器,任何 UPDATE 语句将仅包含实际更改的值,并且只有在实际发生更改时才会创建 UPDATE 语句。

© www.soinside.com 2019 - 2024. All rights reserved.