dapper通过@参数占位符+独立参数对象(如匿名对象或dynamicparameters)实现机制级防sql注入;但order 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是非法语法,必须字符串拼接,但只能来自配置或枚举,绝不能来自用户输入
连接状态和执行前的常见漏点
参数安全了,但连接没开、连上了又提前关了,照样报错或出问题。
- 忘调
connection.Open()直接执行Query<t>()</t>→ 报InvalidOperationException: Connection is not open - 用
using var connection = new SqlConnection(...)却在using块外执行查询 → 连接已释放 - 混用 EF Core 的
DbContext.Database.GetDbConnection()后未检查连接状态 → 可能已关闭 - 事务中必须手动打开连接,且别忘了在
finally里Close()
最容易被忽略的是:表名、排序字段这些“非值位置”,开发者常以为用了 Dapper 就万事大吉,结果在这些地方埋下高危漏洞。参数化只管住“数据”,不管“结构”——结构安全得靠代码逻辑兜底。











