无法跟踪实体类型的实例,因为正在跟踪具有该键值的另一个实例

Tom*_*Tom 7 c# automapper entity-framework-core .net-core

我基本上尝试使用 EntityFrameWork core 和 .Net core 3.1 来实现 CRUD。我的更新操作遇到问题,无法使用修改后的值更新上下文。我正在使用邮递员发起请求。

正如您在下面的代码中看到的,我试图检查该客户是否存在以及它是否确实将修改后的对象传递到上下文。

在此输入图像描述

功能码

   [FunctionName("EditCustomer")]
    public async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Anonymous,"post", Route = "update-customer")] HttpRequest req)
    {
        var customer = JsonConvert.DeserializeObject<CustomerViewModel>(new StreamReader(req.Body).ReadToEnd());
        await _repo.UpdateCustomer(customer);
        return new OkResult();
    }
Run Code Online (Sandbox Code Playgroud)

存储库方法

  public async Task UpdateCustomer(CustomerViewModel customerViewModel)
    {
        if (customerViewModel.CustomerId != null)
        {
            var customer = _context.Customers.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId)).FirstOrDefault();

            if (customer == null)
            {
                throw new Exception("customer not found");
            }
            else
            {
                _context.Customers.Update(_mapper.Map<Customers>(customerViewModel));
                await _context.SaveChangesAsync();

            }
        }
       
    }
Run Code Online (Sandbox Code Playgroud)

测绘

   public class CustomerManagerProfile : Profile
    {
        public CustomerManagerProfile()
        {
            CreateMap<CustomerDetails, CustomerDetailsViewModel>().ReverseMap();
            CreateMap<CustomerOrders, CustomerOrdersViewModel>().ReverseMap();
            CreateMap<CustomerOrderDetails, OrderDetailsViewModel>().ReverseMap();
            CreateMap<Customers, CustomerViewModel>().ReverseMap();
        }
    }
Run Code Online (Sandbox Code Playgroud)

解决方案

public async Task UpdateCustomer(CustomerViewModel customerViewModel)
    {
        if (customerViewModel.CustomerId != null)
        {
            var customer = _context.Customers.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId)).FirstOrDefault();

            if (customer == null)
            {
                throw new Exception("customer not found");
            }
            else
            {
                var customerModel = _mapper.Map<Customers>(customerViewModel);
                _context.Entry<Customers>(customer).State = EntityState.Detached;
                _context.Entry<Customers>(customerModel).State = EntityState.Modified;
                await _context.SaveChangesAsync();

            }
        }
}
  

     
Run Code Online (Sandbox Code Playgroud)

Fla*_*ter 14

实体框架为您跟踪您的实体。为了简单起见,可以将其想象为保存一本字典(针对每个表),其中字典键等于实体的 PK。

问题是您无法在字典中添加相同键的两个项目,并且相同的逻辑适用于 EF 的更改跟踪器。

让我们看看你的存储库:

var customer = _context
                  .Customers
                  .Where(c => c.CustomerId.Equals(customerViewModel.CustomerId))
                  .FirstOrDefault();
Run Code Online (Sandbox Code Playgroud)

从数据库中检索所获取的客户,并且更改跟踪器将其放入他的字典中。

var mappedCustomer = _mapper.Map<Customers>(customerViewModel);
_context.Customers.Update();
Run Code Online (Sandbox Code Playgroud)

为了便于解释,我将您的代码分为两步。

重要的是要认识到 EF 只能保存对跟踪对象的更改。因此,当您调用 时Update,EF 会执行以下检查:

  • 这是与我的更改跟踪器相同(引用相等)的对象吗?
  • 如果是,那么它已经在我的更改跟踪器中。
  • 如果没有,则将此对象添加到我的更改跟踪器中。

在您的情况下,mappedCustomer是与 不同的对象customer,因此 EF 尝试添加mappedCustomer到更改跟踪器。由于customer已经在那里,并且customermappedCustomer具有相同的 PK 值,这会产生冲突。

您看到的异常是该冲突的结果。

由于您不需要实际跟踪原始customer对象(因为 EF 在获取它后不会对其执行任何操作),因此最短的解决方案是告诉 EF 不要跟踪customer

var customer = _context
                  .Customers
                  .AsNoTracking()
                  .Where(c => c.CustomerId.Equals(customerViewModel.CustomerId))
                  .FirstOrDefault();
Run Code Online (Sandbox Code Playgroud)

由于customer现在没有放入更改跟踪器中,因此mappedCustomer不会再引起冲突。

然而,您实际上根本不需要获取该客户。您只想知道它是否存在。customer因此,我们可以这样做,而不是让 EF 获取整个对象:

bool customerExists = _context
                        .Customers
                        .Any(c => c.CustomerId.Equals(customerViewModel.CustomerId));
Run Code Online (Sandbox Code Playgroud)

这也解决了这个问题,因为您从未获取原始文件customer,因此它永远不会被跟踪。它还可以在此过程中为您节省一些带宽。诚然,它本身可以忽略不计,但如果您在代码库中重复这种改进,它可能会变得更加重要。


Mar*_*llo 5

您可以做出的最简单的调整是避免Customers像这样跟踪您的检索:

var customer = _context
    .Customers
    .AsNoTracking() // This method tells EF not to track results of the query.
    .Where(c => c.CustomerId.Equals(customerViewModel.CustomerId))
    .FirstOrDefault();
Run Code Online (Sandbox Code Playgroud)

从代码中并不完全清楚,但我的猜测是您的映射器返回Customer具有相同 ID 的新实例,这使 EF 感到困惑。如果您想修改同一个实例,您的调用.Update()也应该有效:

var customer = _context.Customers.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId)).FirstOrDefault();
customer.Name = "UpdatedName"; // An example.
_context.Customers.Update(customer);
await _context.SaveChangesAsync();
Run Code Online (Sandbox Code Playgroud)

事实上,如果您跟踪您Customer甚至不需要显式调用.Update()方法,跟踪的目的是了解对实体进行了哪些更改并应将其保存到数据库中。因此这也将起作用:

// Customer is being tracked by default.
var customer = _context.Customers.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId)).FirstOrDefault();
customer.Name = "UpdatedName"; // An example.
await _context.SaveChangesAsync();
Run Code Online (Sandbox Code Playgroud)

编辑:
您自己提供的解决方案首先跟踪查询的结果( )Customer实例,然后在写入数据库之前停止跟踪它(也称为分离),而是开始跟踪代表更新的实例Customer并将其标记为已修改。显然,这也有效,但只是一种效率较低且优雅的方式。
事实上,如果您使用这种奇怪的方法,我根本不明白获取您的原因Customer。当然你可以:

if (!(await _context.Customers.AnyAsync(c => c.CustomerId == customerViewModel.CustomerId)))
{
    throw new Exception("customer not found");
}

var customerModel = _mapper.Map<Customers>(customerViewModel);
_context.Customers.Update(customerModel);
await _context.SaveChangesAsync();
Run Code Online (Sandbox Code Playgroud)