dapper通过@参数占位符+匿名对象或dynamicparameters可杜绝sql注入,这是机制级保障;但order by、group by列名及表名等语法结构无法参数化,须白名单校验或配置枚举。

只要参数不进SQL字符串模板,只走 @param 占位符 + 匿名对象或 DynamicParameters,Dapper 就不会产生 SQL 注入——这是机制级保障,不是靠运气。
什么时候必须用匿名对象传参
绝大多数查询场景,匿名对象是最简、最安全的选择。它强制你把所有值和 SQL 模板彻底分离。
-
WHERE条件里的字段值:比如"SELECT * FROM Users WHERE Email = @Email"配new { Email = userInput } -
IN子句的值列表:传new { Ids = new[] { 1, 2, 3 } },Dapper 自动展开为WHERE Id IN (@Ids0, @Ids1, @Ids2) -
LIKE模糊匹配:写"Name LIKE @name",然后传new { name = $"%{search}%" },别拼$"Name LIKE '%{search}%'" - 多条件动态组合时,哪怕只用部分字段,也比拼 SQL 安全:比如
if (status != null) sql += " AND Status = @Status",再统一传new { status }
什么时候非得换 DynamicParameters
匿名对象够用,但遇到这几类情况,它就无能为力,必须上 DynamicParameters。
- 需要输出参数:比如存储过程返回
@TotalCount,就得用Add("@TotalCount", dbType: DbType.Int32, direction: ParameterDirection.Output) - 要传表值参数(
DataTable或自定义IDynamicParameter):匿名对象无法表达结构化表数据 - 参数类型模糊:像
DateTimeOffset、Guid在某些驱动(如早期 Npgsql)下可能被误判为string,需显式指定dbType - 参数名动态生成:比如根据请求字段决定加
@CreatedAfter还是@UpdatedBefore,得用Add()逐个注册
注意:DynamicParameters 本身不增加风险——只要你不调用 AddWithValue 去拼字符串、不把用户输入塞进 SQL 字符串里,它仍是参数化的。
哪些地方根本不能参数化,必须另做防护
@param 只能替值,不能替语法结构。以下三类必须隔离处理,否则再怎么用 DynamicParameters 都没用:
-
ORDER BY后的列名:数据库不支持参数化排序字段,必须白名单校验,比如if (!new[] { "Name", "CreatedAt" }.Contains(sortField)) throw ... -
GROUP BY、HAVING中的列名:同上,不能靠@col替换 - 表名、视图名、schema 名:比如
FROM @tableName是非法语法,必须用字符串拼接,但只能来自配置或枚举,绝不能来自用户输入
这类地方一旦放开自由输入,等于直接绕过所有参数化机制——Dapper 对它完全无感,也不会报错,只会执行恶意 SQL。
最容易被忽略的坑:参数名大小写和空格
Dapper 默认严格匹配参数名,且不忽略空格。这会导致看似正确的代码静默失败或查不到数据。
- SQL 里写
@UserId,但对象传new { userid = 123 }→ 不匹配,参数为空,查询可能返回全表 - SQL 里写
@Name,但传new { Name = " a " }(带首尾空格)→ 值本身含空格是 OK 的,但若 SQL 里误写成@ Name(@ 后有空格),就会报@ Name not found - 使用
DynamicParameters时,Add("UserId", ...)和 SQL 中@userid大小写不一致也会失败(取决于数据库驱动是否大小写敏感)
建议统一用 PascalCase 写 SQL 中的参数名,并在团队内约定命名风格;调试时可临时加 command.Parameters.Count 检查实际绑定的参数数量,比猜更可靠。











