Dapper 本身不支持批量更新,需借助原生 sql、表值参数(TVP)、临时表或第三方库(如 RepoDB、Dapper.FastCRUD)实现;推荐 TVP + JOIN 方式,避免 foreach 循环和字符串拼接。

Dapper 本身不内置批量更新(Bulk Update)功能,它是一个轻量级 ORM,专注于单条 SQL 执行和映射。要实现高效批量更新,需结合原生 SQL、表值参数(TVP)、临时表或第三方扩展库(如 Dapper.FastCRUD、Dapper.Contrib 或更主流的 SqlKata + sqlserver 配合 TVP)。核心思路是:避免循环执行单条 UPDATE,改用一次 SQL 操作更新多行。
使用表值参数(TVP)+ 原生 SQL(推荐,SQL Server)
这是最常用、性能好且可控性强的方式。先在 SQL Server 中创建用户定义表类型(UDT),再通过 Datatable 或自定义 IDataReader 传入多行数据,在 UPDATE 中 JOIN TVP 完成批量更新。
- 在数据库中提前建好 TVP 类型,例如:CREATE TYPE dbo.UpdateUserTableType AS TABLE (Id int, Name NVARCHAR(50), Email NVARCHAR(100));
- C# 中构造 DataTable,列名/类型严格匹配 TVP;或用 Dapper.SqlMapper.Execute 配合 new SqlMapper.ICustomQueryParameter 实现高效传参
- SQL 写法示例:UPDATE u SET u.Name = t.Name, u.Email = t.Email FROM Users u INNER JOIN @updates t ON u.Id = t.Id;
拼接动态 SQL(小批量、简单场景)
适用于更新数据量较小(比如 ≤ 100 条)、字段固定、且对 SQL 注入有严格校验的场景。用 StringBuilder 拼出 CASE WHEN 或 VALUES + JOIN 形式。
- 例如用 UPDATE t SET col = CASE id WHEN 1 THEN ‘a’ WHEN 2 THEN ‘b’ END WHERE id IN (1,2)
- 注意参数化:所有值必须通过 @p0, @p1… 参数传入,不可字符串拼接值
- Dapper 支持匿名对象或 DynamicParameters,可配合 Dictionary 或 List
借助成熟扩展库(省事但引入依赖)
如果项目允许引入第三方包,Dapper.FastCRUD 和 RepoDB 提供了开箱即用的 BulkUpdate 方法(底层仍走 TVP 或 SqlBulkCopy)。
- connection.BulkUpdate(users, batchSize: 1000); —— RepoDB 示例,支持实体类直接更新
- Dapper.FastCRUD 要求实体有主键标记,调用 UpdateAsync(IEnumerable
) 即可 - 注意:这些库在非 SQL Server 环境(如 postgresql、mysql)行为可能受限,需查文档确认支持情况
避坑提醒:什么方式不推荐
以下做法看似简单,实际在生产环境应避免:
- 用 foreach 循环调用 connection.Execute(“UPDATE …”, item) —— N+1 查询,性能极差
- 手动拼接 SQL 字符串并插入值(未参数化)—— SQL 注入高危
- 用 INSERT INTO … select + MERGE 替代 UPDATE,逻辑复杂且易出错,除非真需要 upsert 语义
基本上就这些。真正高效的 Dapper 批量更新,关键不在“用哪个库”,而在于是否绕开了逐条执行、是否利用了数据库原生批量能力(如 TVP、MERGE、SqlBulkCopy)。选型时优先考虑数据库兼容性、团队熟悉度和维护成本。