SqlCommand.Parameters.Add() 不能拼字符串,因其本质是类型化绑定而非文本拼接;用户输入必须全走参数,LIKE需将通配符置于C#变量值中传入,动态SQL片段须白名单校验。

SqlCommand.Parameters.Add() 为什么不能直接拼字符串
因为 SqlCommand.Parameters.Add() 的本质是把参数值交给 SQL Server 驱动层做类型化绑定,而不是在 C# 侧拼接进 SQL 文本。一旦你写成 "WHERE id = " + userInput,哪怕后面加了 Parameters.Add(),漏洞已经产生——SQL 解析器根本没机会看到那个参数。
常见错误现象:SqlException: Incorrect syntax near '123' 或更隐蔽的逻辑绕过(比如输入 1 OR 1=1 后查询返回全部数据)。
- 必须把所有用户可控内容(表单输入、URL 查询参数、JSON 字段值)都走
Parameters.Add()或Parameters.AddWithValue() -
AddWithValue()会自动推断类型,但对NULL、DateTime和字符串长度敏感,生产环境建议用Add()显式指定SqlDbType - 参数名必须以
@开头,且 SQL 语句中引用时也必须带@,比如WHERE name = @name
参数名重复或顺序错乱会导致什么
SQL Server 不依赖参数添加顺序,只认名字。但如果你写了两个 @id,第二次调用 Add() 会抛出 ArgumentException: An SqlParameter with the same parameter name already exists;如果 SQL 里写了 @status 却没添加对应参数,则报 SqlException: Must declare the scalar variable "@status"。
使用场景:批量更新、动态 WHERE 条件组合时容易出错。
- 每次执行前清空参数:用
command.Parameters.Clear(),尤其在复用SqlCommand实例时 - 避免硬编码字符串拼接参数名,用常量或枚举管理,比如
const string ParamName = "@userId"; - 调试时可打印
command.Parameters.Count和循环输出p.ParameterName确认是否齐全
LIKE 查询中通配符怎么安全传参
很多人以为 WHERE name LIKE '%'+@keyword+'%' 没问题,其实这是错的——SQL Server 会把 + 当作字符串拼接操作符,而 @keyword 还没被替换,导致语法错误或意外匹配。
正确做法是把通配符包含在 C# 变量值里,再作为参数传入。
string keyword = userInput.Trim();
command.CommandText = "SELECT * FROM Users WHERE name LIKE @pattern";
command.Parameters.Add("@pattern", SqlDbType.NVarChar).Value = $"%{keyword}%";- 不要在 SQL 字符串里拼
%,否则参数失去意义,且可能触发截断(如NVarChar(10)存不下长 pattern) - 如果需要前缀匹配(
STARTS WITH),就用"{keyword}%";后缀匹配用"%{keyword}" - 注意
SqlDbType.NVarChar对中文友好,VarChar可能导致乱码或隐式转换开销
存储过程调用时参数方向和返回值处理
调用带 OUTPUT 参数或返回值的存储过程时,仅靠 Add() 不够,必须设置 Direction 和 Value 初始值(对 OUTPUT/RETURN_VALUE 类型)。
容易踩的坑:忘记设 Direction 导致参数始终为 NULL,或读取 ReturnValue 前没执行 ExecuteNonQuery()。
-
ParameterDirection.Output的参数,需在执行后读取p.Value;ParameterDirection.ReturnValue的,执行完从p.Value读整数返回码 - 对于
OUTPUT参数,Value必须初始化(哪怕设为DBNull.Value),否则抛InvalidOperationException - 存储过程中用
SET @outParam = ...赋值,C# 才能拿到结果;仅SELECT @outParam不生效
参数绑定不是加个 @ 就完事,关键在「整个用户输入路径」是否彻底脱离 SQL 字符串拼接。最易忽略的是动态构建 SQL 片段(比如排序字段、分页偏移)——这些根本不能参数化,得用白名单校验或 ORM 抽象层兜底。


















