使用实体框架更新带有子集合的对象,导致数据库中出现重复
本文关键字:数据库 对象 框架 实体 更新 子集合 | 更新日期: 2023-09-27 18:17:33
我有一个与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");
}
这不起作用,并在DB中的Addresses
表中创建重复条目。我想我明白为什么(EF不够聪明,不知道集合内的地址需要根据情况添加/修改/删除)。那么,解决这个问题的最好方法是什么?
我的直觉是,我需要迭代Addresses
集合并手动比较它们,从表单中添加任何不存在的客户新集合,更新存在的集合,并删除那些不是由表单发送但存在于DB中的客户集合。类似于(暂时忽略删除功能):
foreach(Address address in customer.Addresses)
{
if (address.Id == 0)
// Add record
else
// Fetch address record from DB
// Update data
}
// Save context
这是最好的方法去做这个,或者有任何EF技巧迭代和同步子集合到DB?
哦,还有一个问题让我挠头-我可以理解一个新的地址记录是如何在数据库中创建的,但我没有得到的是现有的地址记录也被更新为其customer_id
设置为NULL…这到底是怎么发生的?这让我相信EF确实看到原始地址记录以某种方式链接(因为它正在修改它),但它不够聪明,无法实现我传递的记录应该取代它?
谢谢,这是EF6和MVC5
问题来自
这一行 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
}
}
}
这是最好的方法去做这个,或者有任何EF技巧迭代和同步子集合到DB?
不,没有这样的把戏。EF设计师把保存分离实体的任务完全交给了我们——开发者。
然而,有一个名为GraphDiff的包可以解决这个问题,所以你可以尝试一下。下面是使用它的代码的样子:
using RefactorThis.GraphDiff;
...
_context.UpdateGraph(customer, map => map.OwnedCollection(
e => e.Addresses, with => with.AssociatedEntity(e => e.Customer)));
_context.SaveChanges();