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

如何在Web API的Patch请求中实现MongoDB文档部分更新

问题需求

我希望通过Patch请求仅更新MongoDB文档中发生变更的字段,每次请求的变更字段不固定——比如有时更新Balance,有时更新AccountHolder等。需要用单个方法实现任意指定字段的更新,不用为每个字段单独写方法。

注意:IDto接口是空的,仅用于区分Dto类和其他类。


相关代码

UpdateAccountDto.cs

public class UpdateAccountDto : IDto
{
    public string Id { get; set; } = string.Empty;
    public string AccountId { get; set; } = string.Empty;

    public string AccountHolder { get; set; } = string.Empty;

    public string AccountType { get; set; } = string.Empty;

    public decimal Balance { get; set; }
}

Account.cs(实体类)

public class Account
{
    [BsonId]
    [BsonRepresentation(BsonType.ObjectId)]
    public string Id { get; set; } = string.Empty;
    [BsonElement("account_id")] 
    public string AccountId { get; set; } = string.Empty;

    [BsonElement("account_holder")]
    public string AccountHolder { get; set; } = string.Empty;

    [BsonElement("account_type")]
    public string AccountType { get; set; } = string.Empty;
    
    [BsonRepresentation(BsonType.Decimal128)]
    [BsonElement("balance")]
    public decimal Balance { get; set; }
}

接口端点代码

[HttpPatch("UpdatePartialAccount")]
public async Task<ActionResult> UpdatePartialAccount([FromQuery]string id,[FromBody] JsonPatchDocument<UpdateAccountDto>? document)
{
    if (document is null)
        return BadRequest();

    var updateAccountDto = document.ToDto();

    document.ApplyTo(updateAccountDto, ModelState);

    if (!ModelState.IsValid)
        return BadRequest();

    var entity = updateAccountDto.ToEntity<Account>();
    entity.Id = id;

    await _accountRepository.PartialUpdateAsync(entity);

    return NoContent();
}

PartialUpdateAsync方法(未完成)

public async Task<UpdateResult> PartialUpdateAsync(Account account)
{
    //var filter = Builders<Account>.Filter.Eq(a => a.Id, account.Id);
    //var update = Builders<Account>.Update.Set()
    //Partial update
}

解决方案

当前实现的问题在于:将Dto转换为实体后,所有未变更字段都是默认值(空字符串、0),直接用这个实体更新会覆盖数据库中原有值。正确的做法是从JsonPatchDocument中提取实际变更的操作,再构建MongoDB的更新语句,只修改需要变更的字段。

步骤1:修改接口端点

直接传递JsonPatchDocument到仓库,先做有效性验证:

[HttpPatch("UpdatePartialAccount")]
public async Task<ActionResult> UpdatePartialAccount([FromQuery] string id, [FromBody] JsonPatchDocument<UpdateAccountDto>? document)
{
    if (document is null)
        return BadRequest();

    // 验证Patch操作的有效性,避免传入非法值
    var validationDto = new UpdateAccountDto();
    document.ApplyTo(validationDto, ModelState);
    if (!ModelState.IsValid)
        return BadRequest(ModelState);

    await _accountRepository.PartialUpdateAsync(id, document);

    return NoContent();
}

步骤2:实现PartialUpdateAsync方法

解析Patch操作,映射到MongoDB实体的字段(考虑BsonElement别名),构建仅包含变更字段的更新语句:

public async Task<UpdateResult> PartialUpdateAsync(string id, JsonPatchDocument<UpdateAccountDto> patchDocument)
{
    var filter = Builders<Account>.Filter.Eq(a => a.Id, id);
    var updateBuilder = Builders<Account>.Update;
    var updateDefinitions = new List<UpdateDefinition<Account>>();

    foreach (var op in patchDocument.Operations)
    {
        // 提取Dto字段名,映射到实体的Bson字段名
        var dtoFieldName = op.path.TrimStart('/');
        var entityFieldName = GetBsonFieldName<Account>(dtoFieldName);

        // 根据Patch操作类型执行对应更新
        switch (op.op.ToLower())
        {
            case "replace":
            case "add":
                updateDefinitions.Add(updateBuilder.Set(entityFieldName, op.value));
                break;
            case "remove":
                // 根据业务需求处理:设默认值或Unset字段
                if (entityFieldName == "balance")
                    updateDefinitions.Add(updateBuilder.Set(entityFieldName, 0m));
                else
                    updateDefinitions.Add(updateBuilder.Set(entityFieldName, string.Empty));
                // 若允许字段为null,可改用:updateBuilder.Unset(entityFieldName)
                break;
            // 按需支持其他Patch操作(如copy、move)
        }
    }

    if (updateDefinitions.Count == 0)
        return new UpdateResult.Acknowledged(0, 0, null);

    var combinedUpdate = updateBuilder.Combine(updateDefinitions);
    return await _mongoCollection.UpdateOneAsync(filter, combinedUpdate);
}

// 辅助方法:自动获取实体字段的BsonElement名称
private string GetBsonFieldName<T>(string propertyName)
{
    var property = typeof(T).GetProperty(propertyName);
    if (property == null)
        return propertyName;

    var bsonAttr = property.GetCustomAttribute<BsonElementAttribute>();
    return bsonAttr?.ElementName ?? propertyName;
}

关键说明

  • 无需硬编码字段映射:通过反射和BsonElementAttribute自动获取实体的数据库字段名,扩展性更强。
  • 仅更新变更字段:只处理JsonPatchDocument中的操作,不会覆盖未变更的数据库字段。
  • 有效性验证:先将Patch操作应用到空Dto上验证,避免传入非法值(比如给Balance传字符串)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 18:52:37