DynamoDBVersion属性结合条件更新的乐观锁失效问题咨询
你遇到的问题很典型:高层的SaveAsync方法会自动处理[DynamoDBVersion]属性的所有逻辑——包括生成乐观锁的条件表达式、递增版本号,以及自动维护这个字段在数据库中的存在。但当你切换到低级别的UpdateItemAsync时,这些自动行为就消失了,需要我们手动把这些逻辑加回去。
下面分两种方式解决你的问题:
一、结合DynamoDBVersion属性与自定义条件
其实可以借助DynamoDBContext的辅助能力,让它帮你生成乐观锁对应的条件表达式,再和你的自定义条件合并。具体步骤如下:
生成乐观锁的条件表达式
利用DynamoDBContext的CreateSaveExpression方法,它会为带有[DynamoDBVersion]的实体生成对应的乐观锁条件:var saveExpr = _dbContext.CreateSaveExpression(item); // saveExpr.ConditionalExpression 就是乐观锁的条件(比如 Version = :v0)合并自定义条件与乐观锁条件
把你自己的条件和乐观锁条件用AND组合起来,然后传入UpdateItemOperationConfig:var table = _dbContext.GetTargetTable<MyItem>(); var doc = _dbContext.ToDocument(item); // 合并条件 var combinedConditional = string.IsNullOrEmpty(saveExpr.ConditionalExpression) ? "你的自定义条件" : $"{saveExpr.ConditionalExpression} AND 你的自定义条件"; var config = new UpdateItemOperationConfig { ConditionalExpression = combinedConditional, // 别忘了把SaveExpression里的参数也加进来 ExpressionAttributeValues = new Dictionary<string, AttributeValue>(saveExpr.ExpressionAttributeValues) }; // 添加你自定义条件的参数(如果有的话) config.ExpressionAttributeValues.Add(":yourParam", new AttributeValue { S = "你的参数值" }); await table.UpdateItemAsync(doc, config);这样既保留了
[DynamoDBVersion]的乐观锁能力,又能使用你的自定义条件。
二、完全手动实现乐观锁(不依赖高层API)
如果你想彻底用低级SDK实现,需要手动处理版本号的检查和递增:
步骤1:确保读取当前版本号
在更新前,先从数据库读取当前item的Version值(如果是新建item,Version初始为0或者null,需要特殊处理):
var getResponse = await table.GetItemAsync(new Dictionary<string, AttributeValue> { { "Id", new AttributeValue { S = item.Id } } }); var currentVersion = getResponse.Item.TryGetValue("Version", out var v) ? int.Parse(v.N) : 0;
步骤2:构建更新与条件表达式
在更新时,既要检查版本号匹配,又要递增版本号:
var updateConfig = new UpdateItemOperationConfig { // 条件:当前版本等于读取到的版本 ConditionalExpression = "Version = :currentVersion", // 更新:递增版本号,同时更新你需要的其他字段 UpdateExpression = "SET Version = Version + :inc, 其他字段 = :newValue", ExpressionAttributeValues = new Dictionary<string, AttributeValue> { { ":currentVersion", new AttributeValue { N = currentVersion.ToString() } }, { ":inc", new AttributeValue { N = "1" } }, { ":newValue", new AttributeValue { S = item.其他字段 } } } }; try { await table.UpdateItemAsync( new Dictionary<string, AttributeValue> { { "Id", new AttributeValue { S = item.Id } } }, updateConfig ); } catch (ConditionalCheckFailedException ex) { // 乐观锁冲突:处理并发更新的情况,比如重试、返回错误给用户等 Console.WriteLine("并发更新冲突,请重试"); }
注意事项
- 对于新建item,
Version应该初始化为0,并且不需要条件检查(或者条件是attribute_not_exists(Version)) - 必须确保每次更新都递增
Version,否则乐观锁会失效 - 捕获
ConditionalCheckFailedException是处理并发冲突的关键,这是DynamoDB告知你条件不满足的方式
为什么之前的改动会导致Version消失?
高层的SaveAsync会自动把Version字段写入Document,并且在更新时维护它。但_dbContext.ToDocument(item)只会序列化实体中当前有值的属性——如果你的item.Version是null或者没有被正确赋值,生成的Document就不会包含Version字段,数据库里的这个字段自然就消失了。所以在使用低级API时,一定要确保Version字段被正确包含在更新操作中。
内容的提问来源于stack exchange,提问作者Matt Cole

