ASP.NET Core中同时更新含多层子实体的父实体问题
解决ConsignmentLine关联ConsignmentDoc的同步更新问题
搞定Consignment和ConsignmentLine的同步后,要处理ConsignmentLine关联的ConsignmentDocument其实很简单——只需要在处理每个ConsignmentLine的逻辑里,嵌套处理它的文档集合就行。我帮你梳理一下修改步骤和完整代码:
关键修改点
1. 深度加载ConsignmentLine的关联文档
首先要修改查询现有Consignment的代码,确保加载ConsignmentLine下的ConsignmentDocument集合,否则后续无法处理子级文档的更新:
var existingcon = _context.Consignment .Include(cl => cl.ConsignmentLine) .ThenInclude(cld => cld.ConsignmentDocument) // 新增这行,深度加载子级文档 .Include(l => l.Legging) .Include(cd=> cd.ConsignmentDocument) .SingleOrDefault(c => c.Id == consignment.Id);
2. 嵌套处理ConsignmentLine的文档同步
在处理ConsignmentLine的循环中,加入对ConsignmentDocument的新增、更新、删除逻辑:
- 对于已存在的ConsignmentLine:先更新行本身,再同步它的文档集合
- 对于新增的ConsignmentLine:确保文档的外键正确关联到当前Consignment和ConsignmentLine
3. 删除ConsignmentLine时同步删除关联文档
如果数据库未配置外键级联删除,需要手动删除该ConsignmentLine下的所有文档,再删除行本身。
修改后的完整PutConsignment方法
public async Task<IActionResult> PutConsignment(Consignment consignment) { var existingcon = _context.Consignment .Include(cl => cl.ConsignmentLine) .ThenInclude(cld => cld.ConsignmentDocument) // 深度加载ConsignmentLine的文档 .Include(l => l.Legging) .Include(cd=> cd.ConsignmentDocument) .SingleOrDefault(c => c.Id == consignment.Id); if (existingcon == null || existingcon.Id != consignment.Id) { return BadRequest(); } // 更新Consignment主实体 _context.Entry(existingcon).CurrentValues.SetValues(consignment); // 更新或新增ConsignmentLine,同时处理关联的ConsignmentDocument foreach (var conline in consignment.ConsignmentLine) { var existingline = existingcon.ConsignmentLine .Where(x => x.Id == conline.Id) .SingleOrDefault(); if (existingline != null) { // 更新现有ConsignmentLine _context.Entry(existingline).CurrentValues.SetValues(conline); // --- 处理该ConsignmentLine关联的ConsignmentDocument --- // 1. 更新或新增文档 foreach (var newDoc in conline.ConsignmentDocument) { var existingDoc = existingline.ConsignmentDocument .Where(d => d.DocumentId == newDoc.DocumentId) .SingleOrDefault(); if (existingDoc != null) { _context.Entry(existingDoc).CurrentValues.SetValues(newDoc); } else { // 设置外键,确保关联正确 newDoc.ConsignmentLineId = existingline.Id; newDoc.ConsignmentId = existingcon.Id; existingline.ConsignmentDocument.Add(newDoc); } } // 2. 删除不在传入集合中的文档 foreach (var existingDoc in existingline.ConsignmentDocument.ToList()) { if (!conline.ConsignmentDocument.Any(d => d.DocumentId == existingDoc.DocumentId)) { _context.ConsignmentDocument.Remove(existingDoc); } } // --- 文档处理结束 --- } else { // 新增ConsignmentLine existingcon.ConsignmentLine.Add(conline); // 确保新增行的文档外键正确 foreach (var doc in conline.ConsignmentDocument) { doc.ConsignmentId = existingcon.Id; doc.ConsignmentLineId = conline.Id; } } } // 删除ConsignmentLine,同时删除关联文档 foreach (var line in existingcon.ConsignmentLine.ToList()) { if(!consignment.ConsignmentLine.Any(x=> x.Id == line.Id)) { // 先删除该Line下的所有文档 foreach (var doc in line.ConsignmentDocument.ToList()) { _context.ConsignmentDocument.Remove(doc); } _context.ConsignmentLine.Remove(line); } } // 原有Legging处理逻辑(保持不变) foreach (var newleg in consignment.Legging) { var leg = existingcon.Legging .Where(x => x.Id == newleg.Id) .SingleOrDefault(); if (leg != null) { _context.Entry(leg).CurrentValues.SetValues(newleg); } else existingcon.Legging.Add(newleg); } foreach (var leg in existingcon.Legging.ToList()) { if(!consignment.Legging.Any(x=> x.Id == leg.Id)) { _context.Legging.Remove(leg); } } // 原有Consignment直接关联的文档处理逻辑(保持不变) foreach (var newdoc in consignment.ConsignmentDocument) { var doc = existingcon.ConsignmentDocument .Where(x => x.DocumentId == newdoc.DocumentId) .SingleOrDefault(); if (doc != null) { _context.Entry(doc).CurrentValues.SetValues(newdoc); } else existingcon.ConsignmentDocument.Add(newdoc); } foreach (var doc in existingcon.ConsignmentDocument.ToList()) { if (!consignment.ConsignmentDocument.Any(x => x.DocumentId == doc.DocumentId)) { _context.ConsignmentDocument.Remove(doc); } } try { await _context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!ConsignmentExists(consignment.Id)) { return NotFound(); } else { throw; } } return Ok(consignment); }
额外说明
- 如果你的数据库已经为
ConsignmentLineId外键配置了级联删除(ON DELETE CASCADE),那么删除ConsignmentLine时可以省略手动删除文档的步骤,数据库会自动处理。 - 注意确保传入的
ConsignmentLine对象包含完整的ConsignmentDocument集合,否则会误删未传入的文档。
内容的提问来源于stack exchange,提问作者Shaker Kamal




