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

如何实现带版本校验的条件式Upsert:旧版本无操作

带版本校验的条件式Upsert实现需求

需要对MongoDB(兼容Azure Cosmos DB)集合执行条件式Upsert操作:匹配Id不存在则插入新文档,存在则更新,但需满足一个核心限制:若集合中已持久化文档的Version整数属性值大于或等于待Upsert文档的Version,则该Upsert直接无任何操作。

测试文档定义

public record TestDocument(string Id, int Version, string SomeDataString);

预期行为

  • 若集合中无Id="Foo"的文档,Upsert任意Version的Id="Foo"文档,成功插入新文档
  • 若集合中存在Version=1的Id="Foo"文档,UpsertVersion=2的Id="Foo"文档,成功替换现有文档
  • 若集合中存在Version≥2的Id="Foo"文档,UpsertVersion=2的Id="Foo"文档,无任何操作

当前实现方案及问题

目前通过「匹配Id且Version小于待Upsert文档的过滤器执行更新,捕获重复键异常」的方式实现,但该方案存在严重缺陷:

var idMatchFilter = Builders<TestDocument>.Filter.Eq(e => e.Id, upsertDocument.Id);
var versionFilter = Builders<TestDocument>.Filter.Lt(e => e.Version, upsertDocument.Version);
var combinedFilter = Builders<TestDocument>.Filter.And(idMatchFilter, versionFilter);

var replaceOptions = new ReplaceOptions() { IsUpsert = true };
try
{
    await testCollection.ReplaceOneAsync(combinedFilter, upsertDocument, replaceOptions);
}
catch (MongoWriteException mongoWriteException) when (mongoWriteException.Message.Contains("A write operation resulted in an error. WriteError: { Category : \"DuplicateKey\", Code : 11000, Message : \"E11000 duplicate key error collection"))
{
    // 插入旧版本文档时会触发预期异常,但理想状态下应直接无操作而非触发异常
}
  • 批量Upsert场景(如1000个文档)下,频繁捕获异常会导致性能极低,可行性差
  • 文档结构复杂时,需要替换整个文档而非修改个别属性,该方案无法适配更优的批量操作逻辑

测试用例(Xunit)

以下测试用例当前仅通过捕获MongoWriteException才能通过,目标是实现「旧版本文档写入时直接无操作」的逻辑,无需捕获异常:

[Theory]
// 插入:无持久化文档
[InlineData(null, 1, true)]
// 更新:持久化版本更旧
[InlineData(1, 2, true)]
// 更新:持久化版本相同
[InlineData(2, 2, false)]
// 更新:持久化版本更新
[InlineData(2, 1, false)]
public async Task ConditionalUpsert(int? persistedVersion, int upsertVersion, bool shouldUpdate)
{
    // 初始化
    var testCollection = GetTestCollection();
    var emptyFilter = Builders<TestDocument>.Filter.Empty;
    await testCollection.DeleteManyAsync(emptyFilter);
    if (persistedVersion.HasValue)
    {
        // 预存指定版本的测试文档
        var testDocument = new TestDocument("Foo", persistedVersion.Value, "persisted document dummy payload");
        await testCollection.InsertOneAsync(testDocument);
    }
    var upsertDocument = new TestDocument("Foo", upsertVersion, "new document dummy payload");

    // 执行操作
    var idMatchFilter = Builders<TestDocument>.Filter.Eq(e => e.Id, upsertDocument.Id);
    var versionFilter = Builders<TestDocument>.Filter.Lt(e => e.Version, upsertDocument.Version);
    var combinedFilter = Builders<TestDocument>.Filter.And(idMatchFilter, versionFilter);

    // 错误实现:写入旧版本文档时会错误更新已有文档
    //var findOneAndReplaceOptions = new FindOneAndReplaceOptions<TestDocument> { IsUpsert = true };
    //await testCollection.FindOneAndReplaceAsync(idMatchFilter, upsertDocument, findOneAndReplaceOptions);

    // 错误实现:写入旧版本文档时会尝试插入新文档,触发重复键异常
    // Command findAndModify failed: E11000 duplicate key error collection: SomeDatabase.SomeCollection. Failed _id or unique index constraint
    //var findOneAndReplaceOptions = new FindOneAndReplaceOptions<TestDocument> { IsUpsert = true };
    //await testCollection.FindOneAndReplaceAsync(combinedFilter, upsertDocument, findOneAndReplaceOptions);

    var replaceOptions = new ReplaceOptions() { IsUpsert = true };
    try
    {
        // 错误逻辑:当已有文档版本更新时,过滤器匹配不到,会尝试插入旧版本文档,触发异常
        await testCollection.ReplaceOneAsync(combinedFilter, upsertDocument, replaceOptions);
    }
    catch (MongoWriteException mongoWriteException) when (mongoWriteException.Message.Contains("A write operation resulted in an error. WriteError: { Category : \"DuplicateKey\", Code : 11000, Message : \"E11000 duplicate key error collection"))
    {
        // 插入旧版本文档时的预期异常,理想状态下应直接无操作
    }

    // 断言验证
    var allDocuments = await testCollection.Find(emptyFilter).ToListAsync();
    var queriedDocument = Assert.Single(allDocuments);
    var expectedVersion = shouldUpdate ? upsertVersion : persistedVersion;
    Assert.Equal(expectedVersion, queriedDocument.Version);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 18:54:52