匿名对象防注入因dapper将sql模板与参数彻底分离,数据库接收带@占位符的语句和独立参数包,参数作为纯数据绑定不参与sql解析,依赖ado.net参数化能力,同时避免类型错误等问题。

直接用匿名对象传参就能堵住绝大多数 SQL 注入漏洞,前提是别在 SQL 字符串里拼接用户输入。
为什么匿名对象能防注入
Dapper 不是靠“转义引号”或“过滤单引号”来防注入,而是把 SQL 模板和参数值彻底分开。数据库收到的是带占位符的语句(如 SELECT * FROM Users WHERE Name = @Name)和独立的参数包({ Name = "admin' OR '1'='1" }),后者被当作纯数据绑定,不会参与 SQL 解析。
这种机制依赖底层 ADO.NET 的参数化能力,跟数据库驱动强相关 —— 所以它不只防注入,还避免了类型转换错误、时区歧义等隐性问题。
- SQL 中的占位符必须是
@开头,且大小写敏感(尤其 SQLite) - 匿名对象属性名必须与占位符名称完全一致,
new { name = "x" }无法匹配@Name -
null值会自动转为DBNull.Value,不用手动判空
常见错误写法对比
下面这些看着像“用了 Dapper”,其实已经绕过参数化保护:
- ❌
connection.Query($"SELECT * FROM Users WHERE Name = '{name}'")—— 字符串插值直接拼进 SQL,$""是注入入口 - ❌
connection.Query("SELECT * FROM Users WHERE Name = '" + name + "'")—— 经典拼接,毫无防护 - ❌
connection.Query("SELECT * FROM Users WHERE Name = @name", new Dictionary<string object> { ["name"] = name })</string>—— 匿名对象更可靠,Dictionary不支持属性名绑定,Dapper 可能 fallback 到弱匹配甚至报错
✅ 正确写法只有一种核心模式:Query<t>(sql, new { ParamName = value })</t>,其中 ParamName 必须和 SQL 里的 @ParamName 完全一致。
哪些场景不能只靠匿名对象
匿名对象够用,但不是万能。遇到以下情况,必须换用 DynamicParameters:
- 需要输出参数(比如存储过程返回
@TotalCount) - 要传表值参数(
DataTable或自定义SqlMapper.IDynamicParameter实现) - 参数类型模糊(如
DateTimeOffset、Guid在某些数据库驱动下需显式指定DbType) - 动态构建参数列表(比如根据条件决定是否加
@Status)
注意:即使用了 DynamicParameters,只要不调用 AddWithValue 拼接字符串、不把用户输入塞进 SQL 模板本身,依然安全。它的本质仍是参数化,只是比匿名对象更可控。
表名、列名、ORDER BY 这类不能参数化的怎么办
@ 占位符只能用于值,不能用于标识符(表名、列名、排序字段、GROUP BY 子句等)。如果业务真需要动态列名,唯一安全做法是白名单校验:
- 提前定义允许的列名集合:
var allowedColumns = new[] { "Name", "Email", "CreatedAt" }; - 从用户输入提取字段名后,严格比对:
if (!allowedColumns.Contains(sortField)) throw new ArgumentException("Invalid sort field"); - 再拼进 SQL:
ORDER BY " + sortField—— 此时已确认是可信值,拼接才安全
漏掉这步,哪怕其他所有参数都用匿名对象,整个查询仍可能被注入。这是最容易被忽略的盲点。











