Dapper中IN子句必须用原生参数化(如new { Ids = ids }),严禁字符串拼接,否则导致SQL注入;空数组需提前判断,超长列表应分批或用临时表;动态WHERE中结构可拼、数据必参数化。

IN子句必须用 Dapper 原生参数化,不能拼字符串
直接把用户传入的 ID 列表拼成 "WHERE Id IN (1,2,3)" 或 "WHERE Id IN ('" + string.Join("','", ids) + "')" 是 100% 的 SQL 注入入口。哪怕 ids 是 int[],一旦中间混入恶意字符串(比如从 URL 解析出未校验的字符串数组),就可能变成 "1', DROP TABLE users--"。
Dapper 对 IN 的支持是机制级的:只要传 new { Ids = new[] { 1, 2, 3 } },它会自动展开为 WHERE Id IN (@Ids0, @Ids1, @Ids2),每个值单独参数化,数据库根本不会把它当 SQL 代码执行。
- 空数组会生成
WHERE Id IN ()→ 报错,需提前判断并跳过条件或改用1=0 - 超长数组(如上万 ID)可能触发 SQL Server 参数数限制(2100),得拆批或改用临时表
- PostgreSQL 对参数个数无硬限制,但性能随参数增多下降,建议单次不超过 1000 个
DynamicParameters 也能安全处理 IN,但没必要
有人觉得 DynamicParameters 更“可控”,于是手动循环 Add() 每个值,比如:
var p = new DynamicParameters();
p.Add("@id0", 1); p.Add("@id1", 2); // ...这完全多余,且易出错。Dapper 的 new { Ids = ids } 写法更短、更可靠,还自动处理 null、empty 和类型推导。只有当你需要混合输出参数 + IN 列表时,才值得切到 DynamicParameters。
- 别用
AddWithValue()往里塞数组——它会把整个数组当一个对象传,不是展开 - 若 IDs 来自字符串(如
"1,2,3"),先Split(',').Select(int.Parse),再传数组,别留字符串在 SQL 层解析 - 整型字段传字符串数组(如
new[] { "1", "2" })会导致 PostgreSQL 类型错误、MySQL 索引失效
动态 WHERE + IN 组合时,仍要分离结构与数据
比如用户可选按 ID 列表查,也可选按状态查,SQL 要动态拼接。关键原则是:WHERE 关键字和字段名可以拼,所有值必须参数化。
正确做法:
var sql = "SELECT * FROM Orders WHERE 1=1";
var parameters = new DynamicParameters();
<p>if (orderIds?.Length > 0)
{
sql += " AND Id IN @OrderIds";
parameters.Add("@OrderIds", orderIds);
}</p><p>if (!string.IsNullOrEmpty(status))
{
sql += " AND Status = @Status";
parameters.Add("@Status", status);
}</p><p>var orders = connection.Query<Order>(sql, parameters);- 拼的是
" AND Id IN @OrderIds",不是" AND Id IN (" + string.Join(",", orderIds) + ")" -
@OrderIds必须是数组或集合,不能是逗号分隔字符串 - 如果
orderIds是null或空,@OrderIds传进去 Dapper 会报错,得提前过滤掉该条件分支
复杂场景推荐 DapperQueryBuilder,但别迷信“插值”
DapperQueryBuilder 允许写 WHERE Name LIKE {search} 这种语法,看起来像拼字符串,实际它内部仍转成参数化。但它只对白名单内的变量名做转换,不支持运行时计算的表达式,比如 {search?.Trim() ?? ""} 就会失败。
- 它适合固定字段、简单条件的管理后台查询,不适合字段名/表名也动态的场景
- 仍要自己校验
search是否为空、是否含通配符(%应由业务逻辑加,而非前端传入) - 它不解决
ORDER BY @column这类问题——列名永远不能参数化,必须走白名单枚举或正则校验
最常被忽略的一点:IN 子句的安全性不取决于你用不用 DynamicParameters,而取决于你有没有让任何一个用户输入字符出现在最终 SQL 字符串里。只要没出现,就安全;一旦出现,不管后面加多少层封装,都已失守。

















