使用Entity Framework更新具有子集合的对象,从而导致数据库中出现重复项

时间:2016-11-10 19:58:40

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

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

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

2 个答案:

答案 0 :(得分:2)

问题来自

     existingCustomer.Addresses = customer.Addresses;
你的代码中的

。这就像来自模型的Addresses中的字段customer一样。到目前为止还好。关键是customer此时与数据库模型没有任何关系(它不是来自数据库而是来自视图)。

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

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
      }
   }
}

答案 1 :(得分:2)

  

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

不,没有这样的伎俩。 EF设计师将完全取消的实体留给了我们 - 开发人员。

但是有一个名为GraphDiff的软件包可以解决这个问题,所以你可以尝试一下。以下是您的代码使用它的方式:

using RefactorThis.GraphDiff;

...

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

_context.SaveChanges();