字符串拼接SQL在C#中等于埋雷,因用户输入直接拼入SQL会导致语法错误或SQL注入;根本原因是SQL引擎不区分代码与数据,必须用SqlCommand.Parameters参数化查询,而非字符串处理。

为什么字符串拼接 SQL 在 C# 里等于埋雷
因为用户输入直接进 string.Format 或 + 拼出来的 SQL,数据库就当真实命令执行。一个单引号就能让 WHERE name = 'O'Connor' 直接语法报错;更危险的是 ' OR 1=1 -- 这类注入,可能删库、拖库、越权查数据。
根本原因不是 C# 语言问题,而是 SQL 引擎不区分“代码”和“数据”。你拼进去的,它全当 SQL 解析。
- 所有用户可控输入(
TextBox.Text、Request.QueryString、Model.Id)都不能直接拼进 SQL 字符串 - 即使加了
SqlHelper.Escape类的自定义转义函数也不安全——不同数据库转义规则不同,且容易漏掉边界情况 -
SqlCommand.CommandText只接受原始 SQL 字符串,它本身不校验内容;安全靠的是参数化机制,不是字符串处理
C# 中必须用 SqlCommand.Parameters.Add 的三种写法
参数化查询的核心是把值和 SQL 结构分开:SQL 里只留占位符,值通过 Parameters 集合传入。数据库驱动层负责把值安全地绑定过去,自动处理引号、类型转换、编码等。
- 推荐写法:
cmd.Parameters.AddWithValue("@name", userName)—— 简单场景够用,但注意类型推断不准(比如传null会推成Object,可能触发隐式转换失败) - 更稳写法:
cmd.Parameters.Add("@age", SqlDbType.Int).Value = userAge—— 显式指定 SQL 类型,避免因 .NET 类型映射偏差导致查询计划缓存失效或比较异常 - 批量插入时别用
AddWithValue循环:同一参数名重复添加会抛ArgumentException;应改用cmd.Parameters.Clear()或为每个参数用唯一名称(如@name1,@name2)
示例:
var cmd = new SqlCommand("SELECT * FROM Users WHERE Status = @status AND CreatedAt > @since", conn);
cmd.Parameters.Add("@status", SqlDbType.VarChar, 20).Value = "active";
cmd.Parameters.Add("@since", SqlDbType.DateTime2).Value = DateTime.UtcNow.AddDays(-7);
Where 条件动态拼接时怎么保参数化
不能因为“条件不确定”就退回到字符串拼接。动态 SQL 一样能参数化,关键是把占位符和参数添加逻辑对齐。
- 先构建 SQL 片段列表和参数列表两个集合,比如
var whereParts = new List(); var parameters = new List (); - 每加一个条件,同步追加占位符(如
"Status = @status")和对应SqlParameter对象到各自列表 - 最后用
string.Join(" AND ", whereParts)拼 WHERE 子句,再把所有SqlParameter批量加进cmd.Parameters - 切忌用
string.Concat把参数值塞进 SQL 字符串里——哪怕你“确认过输入是数字”,也违反参数化原则
错误示范:"WHERE Id IN (" + string.Join(",", ids) + ")" → 正确做法是生成 @id0, @id1 等多个参数,或用表值参数(DataTable + SqlDbType.Structured)
Entity Framework 和 Dapper 里参数化是不是自动的
是,但有前提。EF 的 LINQ to Entities 查询(如 context.Users.Where(u => u.Name == name))默认走参数化;Dapper 的 Query 也自动处理(param 是匿名对象或 IDictionary)。
- EF 中一旦用了
FromSqlRaw或ExecuteSqlRaw,就必须手动参数化:写"WHERE Name = {0}"是错的,要写"WHERE Name = @name"并传new { name = input } - Dapper 如果用字符串插值(
$"SELECT * FROM X WHERE id = {id}"),就是硬拼,完全绕过参数化,和手写 ADO.NET 拼接一样危险 - EF Core 7+ 支持原生 SQL 的
InterpolatedString语法(context.Users.FromSqlInterpolated($"...")),这个才是安全的,底层自动转参数
最易被忽略的一点:日志里看到的 SQL(比如 EF 的 LogTo 输出)常显示参数值“展开”后的样子,但这只是日志格式化效果,实际发给数据库的仍是带 @ 占位符的语句 + 分离参数包。










