Dapper 的 Execute 不支持真正批量更新,正确做法是将逻辑下推至数据库端,如用表值参数(TVP)配合 JOIN 更新;避免循环执行或依赖 Dapper.Contrib 等伪批量方案;注意连接复用、分批事务及索引优化。
批量更新不能直接用 Dapper 的 Execute 调用单条 SQL 更新多行
dapper 本身不提供原生的批量更新 api,execute 方法每次只执行一条 sql 语句,哪怕你拼了 100 个 update 语句用分号隔开,在大多数数据库(如 sql server、postgresql)里也会被拒绝或只执行第一条。这不是 dapper 的限制,是底层 ado.net 和数据库协议决定的。
常见错误现象:SqlException: Incorrect syntax near ';'(SQL Server),或静默只更新第一行。
- 正确做法是:把批量更新逻辑下推到数据库端,用
UPDATE ... FROM(SQL Server)、UPDATE ... JOIN(MySQL)、或 CTE +UPDATE(PostgreSQL) - 或者用临时表/表值参数(TVP)一次性传入数据集,再在 SQL 中关联更新
- 避免在 C# 里循环调用
Execute,哪怕加了事务,I/O 和网络往返开销也极大
用表值参数(TVP)实现高效批量更新(SQL Server 场景)
这是 SQL Server 下最可控、性能最好的方式,核心是让数据库自己完成“匹配 + 更新”,C# 只负责传数据。
使用前提:数据库已建好对应类型的表值参数(例如 CREATE TYPE dbo.IdNameList AS TABLE (Id INT, Name NVARCHAR(50)))
- 在 C# 中构造
DataTable,列名和类型必须与 TVP 完全一致(包括 nullability) - 用
new SqlMapper.IDynamicParameters()添加参数,类型设为SqlDbType.Structured,并指定TypeName为你的 TVP 名称 - SQL 语句写成
UPDATE t SET t.Name = src.Name FROM Users t INNER JOIN @updates src ON t.Id = src.Id - 注意:TVP 不支持跨数据库引用,也不能在链接服务器查询中直接使用
示例片段:
var table = new DataTable();
table.Columns.Add("Id", typeof(int));
table.Columns.Add("Name", typeof(string));
foreach (var item in updates) {
table.Rows.Add(item.Id, item.Name);
}
var p = new DynamicParameters();
p.Add("@updates", table.AsEnumerable(), DbType.Object, ParameterDirection.Input, null, null, "dbo.IdNameList");
connection.Execute("UPDATE t SET t.Name = src.Name FROM Users t INNER JOIN @updates src ON t.Id = src.Id", p);用 Dapper.Contrib 或 Dapper.FastCRUD 替代?别盲目引入
Dapper.Contrib 的 UpdateAll 看似方便,但实际是循环调用单条 UPDATE,没有真正批量;Dapper.FastCRUD 支持批量更新,但依赖自动生成 SQL,对复杂条件(比如 where 子句含函数、多表关联)支持弱,且调试困难。
- 如果你的场景只是“按主键更新若干字段”,且实体结构稳定,
Dapper.FastCRUD的UpdateAsync可用,但务必开启日志确认生成的 SQL 是你预期的 - 如果更新逻辑含业务判断(如 “仅当状态为 Pending 时才更新”),必须手写 SQL —— 自动生成的 SQL 很难精准控制 WHERE 条件
- 引入新包会增加部署体积和兼容性风险,.NET 6+ 项目要注意
Dapper.FastCRUD对泛型约束和 nullable reference 的适配是否完整
性能关键点:别忽略连接复用、事务粒度和索引
批量更新快不快,一半看 SQL 写法,一半看执行环境。
- 确保连接字符串开启了连接池(默认开启),但别在循环里反复
new SqlConnection—— 即使用了using,频繁创建销毁连接仍拖慢整体吞吐 - 批量更新建议显式包裹在
SqlTransaction中,但事务范围不宜过大(比如 10 万行更新一个事务),否则锁表时间长、日志暴涨、失败回滚代价高;按 1000–5000 行分批更稳妥 - WHERE 条件涉及的字段(如
Id)必须有索引,否则即使 SQL 正确,也会触发全表扫描,性能断崖下跌 - SQL Server 上,若 TVP 数据量超 1 万行,考虑改用
SqlBulkCopy先导入临时表,再用 UPDATE 关联,比 TVP 更快
最容易被忽略的是索引 —— 很多人测完“SQL 执行快”,上线后发现慢十倍,结果发现生产库缺了关键字段的索引。



















