Dapper 不自动处理 rowversion 并发标记,需手动在查询中包含 RowVersion 字段并映射为 byte[],更新时在 WHERE 中显式比对原值,冲突时 rows=0 或 OUTPUT 返回 null,推荐用 OUTPUT 获取新 RowVersion。

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( "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( @"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,能写出高效、明确的乐观并发方案。










