必须使用 SqlParameter 防 SQL 注入,拼接字符串(如 $"...")根本不可进生产;参数化能杜绝 ' OR 1=1 -- 等攻击,且类型强绑定、前端校验无效、动态条件需结构与参数分离。

用 SqlParameter 就行,其他都是绕弯子或掩耳盗铃。
为什么拼接字符串(string.Format、$"...")一定不行
不是“不推荐”,是根本不能进生产环境。只要用户输入能进 SQL 字符串,攻击者就能加 ' OR 1=1 -- 或执行 EXEC xp_cmdshell。C# 里所有 string + "WHERE id = " + userInput 类写法,编译器不报错,但等于在登录接口留了后门。
常见错误现象:SqlException: Incorrect syntax near 'xxx' 看似是格式问题,其实是注入尝试触发了语法崩坏;更危险的是没报错却查出了不该看的数据。
- 参数值不会被解析为 SQL 语法,哪怕传入
"'; DROP TABLE Users; --",数据库也只当它是普通字符串 - 类型强绑定:声明
new SqlParameter("@id", SqlDbType.Int)后,传非数字会直接抛SqlException,而不是静默转成空或 0 - 别信“我前端校验了”——前端校验可被绕过,后端才是唯一防线
SqlCommand.Parameters.Add() 的三种安全写法
核心就一条:参数名必须带 @,且值通过独立对象传入,绝不混在 SQL 文本里。
使用场景:增删改查都适用,尤其 INSERT 多字段、UPDATE 动态条件、IN 子句批量 ID 查询。
-
cmd.Parameters.Add("@name", SqlDbType.NVarChar).Value = userName;—— 显式指定类型,适合对性能/精度有要求的场景 -
cmd.Parameters.AddWithValue("@age", userAge);—— 自动推断类型,方便但有坑:传null可能推成SqlDbType.Int而非SqlDbType.Variant,导致后续比较失败 -
cmd.Parameters.Add(new SqlParameter("@email", email) { DbType = DbType.String });—— 显式构造,控制力最强,适合复杂类型(如DateTimeOffset、自定义 UDT)
动态 WHERE 条件和 IN 列表怎么处理
这是最容易手滑拼 SQL 的地方。没有“万能模板”,但有确定解法。
常见错误现象:"WHERE status IN ('" + string.Join("','", statuses) + "')" —— 单引号、逗号、SQL 关键字全在裸奔。
- 单个动态字段:用
if拼 SQL 片段,但只拼结构(如"AND name = @name"),参数仍走Parameters.Add - IN 列表:SQL Server 不支持参数化数组,必须生成固定数量的参数,如
"WHERE id IN (@id1, @id2, @id3)",再循环Add()对应参数;超过 2000 项建议改用临时表或表值参数(DataTable+SqlDbType.Structured) - 别用 Dapper 的
sql.Contains("@")自动补参——它不校验 SQL 结构,仍可能被UNION SELECT绕过
EF Core 里 FromSqlRaw 和 ExecuteSqlRaw 怎么防注入
EF Core 默认的 LINQ 查询天然免疫,但一旦用到原生 SQL,防线就回到 ADO.NET 阶段。
性能影响:用参数化原生 SQL 和纯 LINQ 查询性能差异极小,别拿“EF 生成 SQL 效率低”当借口手写拼接。
-
context.Users.FromSqlRaw("SELECT * FROM Users WHERE Name = {0}", userName)—— ❌ 错误!这是旧版 EF6 写法,EF Core 已移除字符串格式化支持 -
context.Users.FromSqlRaw("SELECT * FROM Users WHERE Name = {0}", userName)在 EF Core 5+ 直接编译失败 - 正确写法:
context.Users.FromSqlRaw("SELECT * FROM Users WHERE Name = @name", new SqlParameter("@name", userName)) - 注意:
@name必须和SqlParameter构造时的名称完全一致,大小写敏感
最常被忽略的点:存储过程里如果用了 EXEC(@sql) 或 sp_executesql 但没对入参做 QUOTENAME 或参数化,那 C# 层再怎么防都没用——防线得拉通到 T-SQL 层。


















