Dapper怎么处理数据库的rowversion/timestamp Dapper并发标记列

来源:这里教程网 时间:2026-02-21 17:33:12 作者:

Dapper 本身不自动处理

rowversion
(SQL Server)或
timestamp
(旧称,现为
rowversion
)这类并发标记列,但可以很方便地配合它实现乐观并发控制。关键在于:Dapper 不拦截或修改 SQL,你需要自己把
rowversion
字段读出来、传进去、并在 WHERE 条件中显式比对。

读取时带上 rowversion

查询实体时,必须把

rowversion
列包含在 SELECT 中,并映射到实体的 byte[] 或 byte[8] 属性上(SQL Server 的
rowversion
固定 8 字节):

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public byte[] RowVersion { get; set; } // 必须是 byte[],不能是 string 或 long
}

查询示例:

var product = connection.QueryFirstOrDefault<Product>(
    "SELECT Id, Name, RowVersion FROM Products WHERE Id = @id", 
    new { id = 123 });

更新时用 WHERE 比对 RowVersion

执行 UPDATE 时,在 WHERE 子句中加入

RowVersion = @originalRowVersion
,确保只更新未被他人修改过的行:

var rows = connection.Execute(
    @"UPDATE Products 
      SET Name = @name 
      WHERE Id = @id AND RowVersion = @originalRowVersion",
    new { 
        name = "New Name", 
        id = product.Id, 
        originalRowVersion = product.RowVersion 
    });

如果

rows == 0
,说明并发冲突发生(别人已更新,当前 RowVersion 已变),应抛异常或提示重试。

更新后重新获取新 RowVersion(可选但推荐)

SQL Server 支持

OUTPUT
子句,可在 UPDATE 后立即返回新生成的
rowversion
,避免额外查询:

var updated = connection.QueryFirstOrDefault<Product>(
    @"UPDATE Products 
      SET Name = @name 
      OUTPUT INSERTED.Id, INSERTED.Name, INSERTED.RowVersion
      WHERE Id = @id AND RowVersion = @originalRowVersion",
    new { 
        name = "New Name", 
        id = product.Id, 
        originalRowVersion = product.RowVersion 
    });

如果

updated == null
,即更新失败(并发冲突);否则
updated.RowVersion
就是最新值,可用于后续操作。

注意点和常见坑

类型必须是
byte[]
:用
long
string
Guid
映射会出错或值错误
参数名要区分新旧:如
@originalRowVersion
@newRowVersion
(后者一般不用传,DB 自动生成)
不支持 Dapper 的自动乐观并发(像 EF 的
[ConcurrencyCheck]
:一切靠手写 SQL 控制
INSERT 无需处理
rowversion
是数据库自动生成的,INSERT 语句里不要指定该列

基本上就这些。Dapper 的轻量哲学决定了它不封装并发逻辑,但正因如此,你完全掌控 SQL,能写出高效、明确的乐观并发方案。

相关推荐