Azure CosmosDB:无需读取项,仅当Etag不同时执行UpsertItemAsync
解决方案
你的核心问题是混淆了UpsertItemAsync和ReplaceItemAsync的行为,以及ETag参数的生效范围:
需求拆解
你要实现的是:仅对已存在且未被修改的项执行更新操作,不插入新项,也不覆盖已被其他客户端修改的项,同时避免提前读取记录。
问题根源
UpsertItemAsync的设计逻辑是「存在则更新,不存在则插入」——即使设置了IfMatchEtag,当目标项不存在时,CosmosDB会直接忽略ETag校验执行插入,这就是你看到记录总是被替换/插入的原因。
正确实现方案
使用ReplaceItemAsync配合IfMatchEtag参数,该方法仅针对已存在的项执行更新,不会插入新项,且严格校验ETag:
var database = _cosmosClient.GetDatabase("mydatabase"); var container = database.GetContainer("mycontainer"); var partitionKey = new PartitionKey("abcdef"); var requestOptions = new ItemRequestOptions { // 仅当现有项的ETag与指定值完全匹配时才允许更新 IfMatchEtag = "00000000-0000-0000-c718-abcdefghabcdef", // 可选:关闭写入后的内容返回,减少网络开销 EnableContentResponseOnWrite = false }; var model = new { id = "abcdef", property = "some value" }; try { await container.ReplaceItemAsync(model, model.id, partitionKey, requestOptions, ct); } catch (CosmosException ex) { // 处理两种核心异常: switch (ex.StatusCode) { case System.Net.HttpStatusCode.PreconditionFailed: // ETag不匹配,说明项已被其他客户端修改,不执行更新 break; case System.Net.HttpStatusCode.NotFound: // 项不存在,不执行任何操作 break; default: // 其他异常(如权限、网络问题),重新抛出 throw; } }
补充说明
- ETag的正确使用:ETag由CosmosDB服务端生成,每次项的内容变更都会更新,你需要从可信来源获取(如之前的缓存、业务流程中留存的ETag),而非手动设置或从Explorer复制(仅用于测试)。
- 如果需要插入分支:若你确实需要「存在则更新(ETag匹配),不存在则插入」的逻辑,可继续使用
UpsertItemAsync+IfMatchEtag——此时当项不存在时会自动插入,存在时则校验ETag再更新。 - 模型中的
_etag字段:客户端设置_etag = null无意义,CosmosDB在写入时会忽略客户端提供的_etag,仅使用服务端生成的值。
内容的提问来源于stack exchange,提问作者mihails.kuzmins
相关产品推荐
相关产品推荐

