SqlParameter不能直接拼接SQL字符串,否则参数无效且存在注入风险;动态字段/表名需白名单校验,值必须用SqlParameter绑定;IN查询需手动构造参数名或使用表值参数;DateTime空值须转DBNull.Value,字符串空值也应统一处理。

SqlParameter 不能拼接 SQL 字符串
直接用 string.Format 或 $"" 把参数值塞进 SQL 里,哪怕用了 SqlParameter,也照样有注入风险。因为参数对象根本没被用上——SQL 已经在客户端拼好了,数据库只当它是普通语句执行。
常见错误写法:
var sql = $"SELECT * FROM Users WHERE Name LIKE '%{searchTerm}%'"; // 错!
using var cmd = new SqlCommand(sql, conn);
cmd.Parameters.Add(new SqlParameter("@id", userId)); // 这个参数完全没用上
正确做法:所有动态部分(字段名、表名、操作符、排序方向)必须走白名单校验;值一律交给 SqlParameter 绑定。
- 字段/表名:用
switch或字典映射到预设字符串,不接受任意输入 - LIKE 模式:通配符(
%、_)必须由代码拼,不是用户输入的原始值 - IN 列表:无法单个
SqlParameter表达多个值,得用string.Join+ 动态生成参数名,或改用表值参数(TVP)
IN 查询怎么安全传多个值?
SqlParameter 不支持数组或集合直接绑定,WHERE Id IN (@ids) 这种写法会把整个数组转成字符串,查不到数据,还可能报错。
两种可靠方案:
- 手动构造参数名:对每个值生成唯一参数名,如
@id0、@id1,再用string.Join(",", ids.Select((v, i) => $"@id{i}"))拼IN子句 - 用表值参数(TVP):定义用户定义表类型(UDT),把
List转成DataTable,再作为SqlParameter传入,适合 >10 个值的场景
示例(手动参数):
var ids = new[] { 1, 2, 3 };
var paramNames = ids.Select((id, i) => {
cmd.Parameters.Add(new SqlParameter($"@id{i}", id));
return $"@id{i}";
}).ToArray();
var inClause = string.Join(",", paramNames);
var sql = $"SELECT * FROM Orders WHERE Id IN ({inClause})";
DateTime 和 NULL 值容易出错的地方
DateTime 默认值是 DateTime.MinValue(0001-01-01),SQL Server 不接受这个值,会抛 SqlException: The conversion of a datetime2 data type to a datetime data type resulted in an out-of-range value.
NULL 处理也不容忽视:直接传 null 给 SqlParameter 构造函数会报错,必须显式用 DBNull.Value。
- DateTime:先判断是否为默认值,是则传
DBNull.Value,否则传真实值 - 可空类型(
DateTime?):用value ?? (object)DBNull.Value简洁转换 - 字符串空值:空字符串
""和null在业务上常需统一处理为DBNull.Value,避免数据库存一堆空串
复杂条件组合建议用 Dapper 或 EF Core
纯 ADO.NET 手写带大量 AND/OR、嵌套括号、动态列的 SQL,配合 SqlParameter 维护成本极高,极易漏参数、错顺序、少引号。
比如一个搜索接口要支持“姓名模糊+状态筛选+时间范围+多选分类”,手写 SQL + 参数管理很快失控。
- Dapper:用
DynamicParameters可安全添加条件参数,SQL 字符串仍需手写,但参数绑定干净 - EF Core:用 LINQ 构建表达式树,自动翻译 + 参数化,复杂查询可读性高,且天然防注入
- 硬要用 SqlCommand:建议把 SQL 拆成模板片段(如
WHERE条件块单独组装),每段只负责一类逻辑,参数列表同步构建
真正难的不是加几个 SqlParameter,而是让条件分支和参数声明永远保持一致——这点靠人肉检查不可靠,越复杂的查询越需要抽象层兜底。










