You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

删除关联实体后重插是否有更优方案?现有代码优化咨询

产品关联实体更新的实现合理性与优化方案

问题背景

现有应用中通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 01:01:21