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

.NET Core API如何仅更新DTO提交的字段而非全部字段?

解决.NET Core API部分字段更新时null覆盖数据库原有值的问题

问题根源

你当前的代码直接将CompanyUpdateDto映射为全新的Company实体,实体中未提交的字段(比如Description)会被初始化为null。EF Core的ChangeTracker会将整个实体标记为Modified,导致所有字段都被更新到数据库,包括那些你不想修改的null字段。

方案一:常规推荐做法(从数据库获取原实体后合并字段)

这种方法直观易维护,无需修改SaveChangesAsync:

  1. 修改Service的UpdateAsync方法,先从数据库拉取对应ID的原有实体,再仅更新DTO中传入的非空字段:
public async Task UpdateAsync(CompanyUpdateDto updateDto)
{
    var existingCompany = await _context.Companies.FindAsync(updateDto.Id);
    if (existingCompany == null)
    {
        throw new KeyNotFoundException("指定的公司不存在");
    }

    // 仅更新DTO中不为null的字段
    if (!string.IsNullOrEmpty(updateDto.Name))
    {
        existingCompany.Name = updateDto.Name;
    }
    if (!string.IsNullOrEmpty(updateDto.Description))
    {
        existingCompany.Description = updateDto.Description;
    }
    if (updateDto.UpdatedDate.HasValue)
    {
        existingCompany.UpdatedDate = updateDto.UpdatedDate.Value;
    }

    // 自动更新修改时间
    existingCompany.UpdatedDate = DateTime.UtcNow;
    await _context.SaveChangesAsync();
}
  1. 调整Controller代码,直接传入DTO而非映射后的实体:
[HttpPut]
public async Task<IActionResult> Update([FromBody] CompanyUpdateDto updateCompany)
{
    await _service.UpdateAsync(updateCompany);
    return CreateActionResult(CustomResponseDto<CompanyUpdateDto>.Success(204));
}

方案二:在SaveChangesAsync中实现(仅标记实际修改的字段)

如果一定要在SaveChangesAsync中处理,可通过对比实体原始值与当前值,只保留实际变化字段的修改标记:

修改DataContext的重写方法:

public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
    foreach (var entry in ChangeTracker.Entries())
    {
        if (entry.Entity is BaseEntity entityReference)
        {
            switch (entry.State)
            {
                case EntityState.Added:
                    entityReference.CreatedDate = DateTime.UtcNow;
                    break;
                case EntityState.Modified:
                    // 锁定CreatedDate不被修改
                    Entry(entityReference).Property(x => x.CreatedDate).IsModified = false;

                    // 遍历所有属性,仅保留值发生变化的字段为修改状态
                    foreach (var property in entry.Properties)
                    {
                        if (Equals(property.OriginalValue, property.CurrentValue))
                        {
                            property.IsModified = false;
                        }
                    }
                    break;
            }
        }
    }
    return base.SaveChangesAsync(cancellationToken);
}

注意:这个方案需要配合「先从数据库获取原实体,再将DTO字段映射到原实体」的逻辑使用——否则新实体的未提交字段是null,会被EF判定为值发生变化,依然会覆盖数据库原有值。

额外优化:AutoMapper条件映射

如果你使用AutoMapper,可配置仅映射非null字段,避免覆盖原有值:

CreateMap<CompanyUpdateDto, Company>()
    .ForAllMembers(opt => opt.Condition((src, dest, srcMember) => srcMember != null));

结合「获取原实体再映射」的逻辑,就能自动仅更新DTO中提交的字段。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 12:45:36