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

时间:2020-07-16 11:46:10

标签: c# .net-core entity-framework-core

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

如下面的代码所示,我正在尝试检查该客户是否存在,以及是否确实将修改后的对象传递给上下文。

enter image description here

功能代码

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

存储库方法

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

            }
        }
       
    }

映射

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

解决方案

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

            }
        }
       

3 个答案:

答案 0 :(得分:1)

您可以进行的最简单的调整是避免跟踪这样的检索过程中的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();

从代码中还不清楚,但是我想您的映射器将返回一个具有相同ID的Customer的新实例,这会使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();

事实上,如果您跟踪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();

编辑:
您自己提供的解决方案从跟踪查询(Customer)实例的结果开始,然后在写入数据库之前停止跟踪(又被分离),而是开始跟踪代表更新后的{{1} },并将其标记为已修改。显然,它也可以工作,但是这样做的效率和优雅程度较低。
事实上,如果您使用这种奇怪的方法,我根本看不到提取Customer的原因。当然可以:

Customer

答案 1 :(得分:0)

实体框架会为您跟踪实体。为简单起见,可以将其视为保留一个字典(用于每个表),其中字典键等于您实体的PK。

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

让我们看看您的存储库:

var customer = _context
                  .Customers
                  .Where(c => c.CustomerId.Equals(customerViewModel.CustomerId))
                  .FirstOrDefault();

从数据库中检索获取的客户,然后更改跟踪器将其放入其词典中。

var mappedCustomer = _mapper.Map<Customers>(customerViewModel);
_context.Customers.Update();

为便于说明,我将您的代码分为两个步骤。

重要的是要意识到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();

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

但是,您实际上根本不需要获取此客户。您只想知道它是否存在。因此,我们可以执行以下操作,而不是让EF获取整个customer对象:

bool customerExists = _context
                        .Customers
                        .Any(c => c.CustomerId.Equals(customerViewModel.CustomerId));

这也解决了该问题,因为您从未获取原始的customer,因此也永远不会对其进行跟踪。它还可以在此过程中节省一些带宽。公认它本身可以忽略不计,但是如果您在整个代码库中重复进行此改进,则可能会变得更有意义。

答案 2 :(得分:0)

您使用AutoMapper的方式错误。它不是为了从View模型或DTO映射到Entity类而创建的。这会带来很多问题,您现在只面对其中的一个。

如果您的应用程序中具有更复杂的业务逻辑(而不仅仅是覆盖所有字段),那么管理,测试和调试代码中实际发生的事情将是可怕的。您应该编写自己的逻辑并进行一些业务验证,以防您想进行CRUD以外的其他更新。

如果我是我,我将在UpdateFields类中创建Customer方法,该方法将对其进行更新,并最终调用SaveChanges。这取决于您是否使用贫血的实体(反)模式。如果您不希望实体类具有任何方法,则可以创建仅通过某些域验证手动映射您的VM做实体的方法