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

.NET+SQL Server+EF中Advert实体更新并发错误排查

EF Core更新Advert实体时的并发错误排查

在使用.NET结合SQL Server与Entity Framework Core更新Advert实体时遭遇并发错误,业务逻辑为:每个广告需包含一张或多张图片,当前端提交新图片时,删除旧图片并添加新图片。以下是相关实现代码,请问是否存在操作失误?

现有实现代码

更新方法代码

public async Task<ResponseModel<AdvertDto>> Update(AdvertUpdateDto advertUpdateDto, Guid id) {
    var response = new ResponseModel<AdvertDto>();

    // Busca o anúncio existente no banco de dados
    var advert = await _context.Adverts
        .Include(a => a.Imagens)
        .Include(a => a.Opcionais)
        .FirstOrDefaultAsync(a => a.Id == id);

    if (advert == null) {
        response.Dados = null;
        response.Mensagem = "Auncio não encontrado";
        return response;
    }

    
    _context.Photos.RemoveRange(advert.Imagens);


    var novasImagens = advertUpdateDto.Imagens
        .Select(url => new Photo(url.Url, url.Chave))
        .ToList();
    await _context.Photos.AddRangeAsync(novasImagens);

    try {
        await _context.SaveChangesAsync();
    } catch (DbUpdateConcurrencyException) {
        throw new Exception("Erro de concorrência: as informações foram modificadas por outro usuário.");
    }

    response.Mensagem = "Anuncio alterado com sucesso";


    return response;
}

Advert实体类代码

public sealed class Advert
{
    [Key] public Guid Id { get; set; }
    public string Tipo { get; set; }
    public string Marca { get; set; }
    public string Modelo { get; set; }
    public int AnoModelo { get; set; }
    public string Cor { get; set; }
    public string Cep { get; set; }
    public string Cidade { get; set; }
    public string Estado { get; set; }
    public int Preco { get; set; }
    public string Portas { get; set; }
    public int Quilometragem { get; set; }
    public string Descricao { get; set; }
    public string Placa { get; set; }
    public string Cambio { get; set; }
    [DataType(DataType.DateTime)] public DateTime DataCricao { get; set; } = DateTime.UtcNow;
    [DataType(DataType.DateTime)] public DateTime? DataAtualizacao { get; set; }
    public Condition Condicao { get; set; } = Condition.REQUESTED;
    public string Slug { get; set; }
    public bool? Destaque { get; set; }
    public Guid UsuarioId { get; set; }
    public User Usuario { get; set; }
    public ICollection<Photo>? Imagens { get; set; } = [];
    public ICollection<Optional>? Opcionais { get; set; } = [];
    [Timestamp] public byte[] Version { get; set; }

    private Advert() {}

    public Advert(string tipo, string marca, string modelo, int anoModelo, string cor, string cep,
        string cidade, string estado, int preco, string portas, int quilometragem, string descricao, string placa,
        string cambio, DateTime dataAtualizacao, Condition condicao, string slug, bool? destaque, Guid usuarioId,
        User usuario,
        ICollection<Photo>? imagens, ICollection<Optional>? opcionais)
    {
        Id = Guid.NewGuid();
        Tipo = tipo;
        Marca = marca;
        Modelo = modelo;
        AnoModelo = anoModelo;
        Cor = cor;
        Cep = cep;
        Cidade = cidade;
        Estado = estado;
        Preco = preco;
        Portas = portas;
        Quilometragem = quilometragem;
        Descricao = descricao;
        Placa = placa;
        Cambio = cambio;
        DataAtualizacao = dataAtualizacao;
        Condicao = condicao;
        Slug = slug;
        Destaque = destaque;
        UsuarioId = usuarioId;
        Usuario = usuario;
        Imagens = imagens;
        Opcionais = opcionais;
    }
}

存在的操作失误分析

  • 新图片未关联到Advert实体:代码仅删除旧图片并添加新图片到上下文,但未将novasImagens赋值给advert.Imagens,EF Core无法识别这些图片属于当前广告,会导致数据关联失效;同时因为Advert主实体没有任何属性变更,乐观锁的Version字段不会更新,并发检查机制完全失效。
  • 未更新Advert的修改时间:没有设置advert.DataAtualizacao = DateTime.UtcNow,Advert主表数据无变更,EF Core不会生成对Advert表的更新语句,也就不会触发Version字段的并发冲突检查,这是并发异常无法正确触发的核心原因。
  • 并发异常处理不规范:捕获DbUpdateConcurrencyException后直接抛出普通Exception,丢失了原异常的堆栈和上下文信息,不利于定位具体冲突场景。
  • 冗余的Include操作:代码中Include了Opcionais但未处理其更新逻辑,如果业务不需要同步更新可选项,应移除该Include以优化查询性能。

修正后的代码示例

public async Task<ResponseModel<AdvertDto>> Update(AdvertUpdateDto advertUpdateDto, Guid id) {
    var response = new ResponseModel<AdvertDto>();

    var advert = await _context.Adverts
        .Include(a => a.Imagens)
        .FirstOrDefaultAsync(a => a.Id == id);

    if (advert == null) {
        response.Dados = null;
        response.Mensagem = "Anúncio não encontrado";
        return response;
    }

    // 删除旧图片
    _context.Photos.RemoveRange(advert.Imagens);

    // 创建新图片并关联到当前广告
    var novasImagens = advertUpdateDto.Imagens
        .Select(url => new Photo(url.Url, url.Chave))
        .ToList();
    advert.Imagens = novasImagens;

    // 更新广告的修改时间,触发主实体变更
    advert.DataAtualizacao = DateTime.UtcNow;

    try {
        await _context.SaveChangesAsync();
    } catch (DbUpdateConcurrencyException ex) {
        // 包装原异常保留上下文信息
        throw new InvalidOperationException("Erro de concorrência: as informações foram modificadas por outro usuário.", ex);
    }

    response.Mensagem = "Anúncio alterado com sucesso";
    // 映射实体到DTO返回(需确保有对应的映射逻辑)
    response.Dados = _mapper.Map<AdvertDto>(advert);

    return response;
}

内容的提问来源于stack exchange,提问作者Lucas Camargo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 02:15:01