删除关联实体后重插是否有更优方案?现有代码优化咨询
产品关联实体更新的实现合理性与优化方案
问题背景
现有应用中通过POST请求更新/添加产品的关联实体,当前实现逻辑为:先加载产品及其所有关联实体,再删除原有关联,最后插入新关联。核心代码及删除逻辑如下:
现有核心代码
var product = await _productRepository.TrackingQueryableSelect(f => f.Id == request.Id, i => i.ProductCategories, i => i.GuidePrices, i => i.ProductWebSiteMappings, i => i.ProductAssociatedProducts).SingleAsync(ct)
关联实体删除与新增逻辑
if (request.Categories is { Count: > 0 }) { product.ProductCategories.ToList().ForEach(async x => await _productCategoryRepository.DeleteAsync(x, ct)); product.ProductCategories = request.Categories.Select(s => new ProductCategory() { CategoryId = s, DisplayOrder = 1 }).ToList(); } if (request.ProductWebSiteMappings != null) { await _productWebSiteMappingRepository.DeleteRangeAsync(product.ProductWebSiteMappings.ToList(), ct); product.ProductWebSiteMappings = request.ProductWebSiteMappings.Select(s => new ProductWebSiteMapping() { WebSite = s.WebSite, ProductId = product.Id }).ToList(); }
删除方法实现
public async Task<bool> DeleteAsync(TEntity entity, CancellationToken ct = default) { if (await IsExistsAsync(entity.Id, ct)) { _context.Remove(entity); return true; } else { throw new Exception($"Entity with id {entity.Id} not found!"); } } public async Task DeleteRangeAsync(IEnumerable<TEntity> entities, CancellationToken ct = default) { foreach (var entity in entities) { await DeleteAsync(entity, ct); } }
最终通过await _unitOfWork.SaveAsync(ct);提交更改。
疑问解答
1. 当前实现是否合理?能否仅加载主实体,通过ProductId批量删除关联实体?
当前实现不合理,存在以下明显问题:
- 性能浪费:加载所有关联实体增加了数据库查询开销和内存占用,尤其是当关联实体数量较多时,影响显著。
- 异步风险:
ForEach(async x => ...)中的异步lambda不会被ForEach等待,可能导致删除操作未完成就执行集合赋值,引发EF上下文跟踪异常或数据不一致。 - 冗余检查:
DeleteAsync中对已加载的跟踪实体执行IsExistsAsync检查完全多余,额外增加了数据库查询。
可以通过ProductId批量删除关联实体,无需加载所有关联甚至主实体:
EF Core 3.0+支持ExecuteDeleteAsync方法,可直接生成批量DELETE SQL语句,无需加载实体到内存。即使只加载主实体,也能通过主实体的ID直接批量删除关联,甚至可以完全不加载主实体,直接使用请求中的产品ID操作。
2. 当前DeleteRangeAsync实际是循环单删,如何优化该逻辑及整体流程?
当前DeleteRangeAsync循环单删+每次检查存在性的方式效率极低,优化方向如下:
(1)优化删除方法实现
// 移除冗余的存在性检查,跟踪实体必然存在于上下文 public async Task<bool> DeleteAsync(TEntity entity, CancellationToken ct = default) { _context.Remove(entity); return true; } // 直接使用EF的RemoveRange批量移除,替代循环单删 public Task DeleteRangeAsync(IEnumerable<TEntity> entities, CancellationToken ct = default) { _context.RemoveRange(entities); return Task.CompletedTask; }
(2)重构整体关联更新流程
使用批量删除替代加载实体后删除,减少数据库交互次数:
// 处理ProductCategories if (request.Categories is { Count: > 0 }) { // 直接批量删除该产品下的所有分类关联,无需加载实体 await _context.ProductCategories .Where(pc => pc.ProductId == product.Id) .ExecuteDeleteAsync(ct); // 添加新关联 product.ProductCategories = request.Categories.Select(s => new ProductCategory() { CategoryId = s, DisplayOrder = 1 }).ToList(); } // 处理ProductWebSiteMappings if (request.ProductWebSiteMappings != null) { await _context.ProductWebSiteMappings .Where(pwm => pwm.ProductId == product.Id) .ExecuteDeleteAsync(ct); product.ProductWebSiteMappings = request.ProductWebSiteMappings.Select(s => new ProductWebSiteMapping() { WebSite = s.WebSite, ProductId = product.Id }).ToList(); }
(3)进一步优化:无需加载主实体
如果仅更新关联实体,不修改产品本身,可完全不加载主实体,直接通过request.Id操作,性能更优:
// 确保操作在事务中执行,保证原子性 using var transaction = await _context.Database.BeginTransactionAsync(ct); try { // 批量删除旧关联 if (request.Categories is { Count: > 0 }) { await _context.ProductCategories .Where(pc => pc.ProductId == request.Id) .ExecuteDeleteAsync(ct); // 插入新关联 var newCategories = request.Categories.Select(s => new ProductCategory() { ProductId = request.Id, CategoryId = s, DisplayOrder = 1 }).ToList(); await _context.ProductCategories.AddRangeAsync(newCategories, ct); } // 处理ProductWebSiteMappings同理 if (request.ProductWebSiteMappings != null) { await _context.ProductWebSiteMappings .Where(pwm => pwm.ProductId == request.Id) .ExecuteDeleteAsync(ct); var newMappings = request.ProductWebSiteMappings.Select(s => new ProductWebSiteMapping() { ProductId = request.Id, WebSite = s.WebSite }).ToList(); await _context.ProductWebSiteMappings.AddRangeAsync(newMappings, ct); } await _context.SaveChangesAsync(ct); await transaction.CommitAsync(ct); } catch { await transaction.RollbackAsync(ct); throw; }
内容的提问来源于stack exchange,提问作者advapi
相关产品推荐
相关产品推荐

