【问题标题】:Updating object with child collection using Entity Framework causing duplicates in database使用实体框架更新具有子集合的对象导致数据库中的重复
【发布时间】:2016-11-10 19:58:40
【问题描述】:

我有一个与Address 类有关系的Customer 类:

public class Customer
{
    public int Id { get; set; }

    public string Name { get; set; }

    public virtual ICollection<Address> Addresses { get; set; }
}

public class Address
{
    public int Id { get; set; }

    public string Street1 { get; set; }

    //Snip a bunch of properties

    public virtual Customer Customer { get; set; }
}

我有一个编辑表单,其中显示了客户和地址的所有字段。当这个表单被提交时,它会调用控制器中的Edit 方法:

public ActionResult Save(Customer customer)
{
    if (!ModelState.IsValid)
    {
        var viewModel = new CustomerFormViewModel
        {
            Customer = customer,
            CustomerTypes = _context.CustomerTypes.ToList()
        };

        return View("CustomerForm", viewModel);
     }

     if (customer.Id == 0)
         _context.Customers.Add(customer);
     else
     {
         var existingCustomer = _context.Customers
             .Include(c => c.Addresses)
             .Single(c => c.Id == customer.Id);

         existingCustomer.Name = customer.Name;
         existingCustomer.TaxId = customer.TaxId;
         existingCustomer.CustomerTypeId = customer.CustomerTypeId;
         existingCustomer.CreditLimit = customer.CreditLimit;
         existingCustomer.Exempt = customer.Exempt;
         existingCustomer.Addresses = customer.Addresses;
     }

     _context.SaveChanges();

     return RedirectToAction("Index", "Customers");
}

这不起作用并在数据库中的Addresses 表中创建重复条目。我想我明白为什么(EF 不够聪明,无法知道集合中的地址需要根据情况添加/修改/删除)。那么,解决此问题的最佳方法是什么?

我的直觉是,我需要遍历 Addresses 集合并手动比较它们,从表单中添加客户不存在的任何新集合,更新确实存在的集合,并删除不存在的集合由表单发送但存在于客户的数据库中。类似的东西(暂时忽略删除功能):

foreach(Address address in customer.Addresses)
{
    if (address.Id == 0)
        // Add record
    else
        // Fetch address record from DB
        // Update data
    }
    // Save context

这是解决此问题的最佳方法,还是有任何 EF 技巧来迭代和同步子集合到数据库?

哦,还有一个让我摸不着头脑的问题 - 我可以理解如何在数据库中创建新的地址记录,但我不明白的是现有的地址记录也被更新为customer_id 设置为 NULL……这到底是怎么回事?这让我相信 EF 确实看到原始地址记录以某种方式链接(因为它正在修改它)但它还不够聪明,无法意识到我传入的记录应该替换它?

谢谢 -- 还有,这是 EF6 和 MVC5

【问题讨论】:

    标签: c# asp.net-mvc entity-framework


    【解决方案1】:

    问题出在一行

         existingCustomer.Addresses = customer.Addresses;
    

    在您的代码中。这就像从来自模型的customer 分配字段Addresses。到目前为止还可以。关键是customer 此时与数据库模型没有任何关系(它不是来自数据库,而是来自视图)。

    如果您想用来自模型的数据更新existingCustomer.Addresses,您需要合并数据而不是替换它。下面的“伪代码”可能会给你一个方向:

    void MergeAddresses(var existingAddresses, var newAddresses) {
       foreach(var address in newAddresses) {
          if (existingAddresses.Contains(newAddress)) {
             // merge fields if applicable
          }
          else {
             // add field to existingAddresses - be ware to use a "cloned" list
          }
       }
       // now delete items from existing list
       foreach (var address in existingAddresses.CloneList()) {
          if (!newAddresses.Contains(address)) {
             // remove from existingAddresses
          }
       }
    }
    

    【讨论】:

    • 谢谢!所以这类似于我遍历地址并手动比较每个地址的本能——我确实喜欢将它放在辅助方法中的想法。控制器是否适合此操作?另外,当您说“小心使用克隆列表”时,您到底是什么意思?
    • 我个人会将这种逻辑放到一个单独的(即业务层)类中。这将允许在不模拟 MVC 类的情况下进行简单的单元测试。 “注意”更改当前使用“foreach”或其他枚举操作处理的列表。有一个专门的异常(例如:stackoverflow.com/questions/24986550/…
    • 谢谢,我会使用类似的逻辑并让它工作。感谢您的帮助!
    【解决方案2】:

    这是解决此问题的最佳方法,还是有任何 EF 技巧来迭代和同步子集合到数据库?

    不,没有这样的技巧。 EF 设计人员将保存分离的实体完全交给我们 - 开发人员。

    但是有一个名为GraphDiff 的包正在解决这个问题,所以你可以试一试。以下是您的代码在使用时的样子:

    using RefactorThis.GraphDiff;
    
    ...
    
    _context.UpdateGraph(customer, map => map.OwnedCollection(
        e => e.Addresses, with => with.AssociatedEntity(e => e.Customer)));
    
    _context.SaveChanges();
    

    【讨论】:

      猜你喜欢
      • 2015-07-08
      • 1970-01-01
      • 2011-03-06
      • 2011-11-05
      • 1970-01-01
      • 1970-01-01
      • 2015-12-25
      • 1970-01-01
      • 2020-05-14
      相关资源
      最近更新 更多