如何在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
相关产品推荐
相关产品推荐

