Dapper通过@参数占位符+独立参数对象(如匿名对象或DynamicParameters)实现机制级防SQL注入;但ORDER BY、GROUP BY列名及表名等语法结构无法参数化,必须白名单校验。

只要参数不进 SQL 字符串,只走 @param 占位符 + 独立参数对象,Dapper 就不会产生 SQL 注入——这是机制级保障,不是靠运气。
用匿名对象传参是最简、最安全的默认选择
绝大多数查询场景下,new { Prop = value } 足够用,它强制把 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()逐个注册
哪些地方根本不能参数化,必须白名单校验
@param 只能替值,不能替语法结构。这类地方一旦放开自由输入,等于直接绕过所有参数化机制,Dapper 对它完全无感。
-
ORDER BY后的列名:数据库不支持参数化排序字段,必须白名单校验,比如if (!new[] { "Name", "CreatedAt" }.Contains(sortField)) throw -
GROUP BY、HAVING中的列名:同理,不能靠@col替换 -
表名、视图名、schema 名:比如
FROM @tableName是非法语法,必须字符串拼接,但只能来自配置或枚举,绝不能来自用户输入
最容易被忽略的是:表名列名白名单校验不是“可选加固”,而是硬性边界;哪怕参数全走 DynamicParameters,只要在 SQL 字符串里拼了用户可控的字段名,就等于把门钥匙交出去了。

















