在C#开发中,很多初学者会直接使用字符串拼接的方式构造SQL语句,这种方式虽然简单,但存在极大的安全隐患。恶意用户可以通过构造特殊的输入内容,篡改原本的SQL逻辑,这就是常见的SQL注入攻击。而参数化查询是业界公认的预防SQL注入的有效方案,它从根源上避免了参数被解析为SQL指令的问题。

为什么字符串拼接会有SQL注入风险
我们先看一段典型的字符串拼接构造SQL的代码:
// 危险的字符串拼接写法 string userName = "test' OR '1'='1"; string sql = "SELECT * FROM Users WHERE UserName = '" + userName + "'"; // 执行该SQL后,实际执行的语句会变成: // SELECT * FROM Users WHERE UserName = 'test' OR '1'='1' // 此时会绕过用户名验证,返回所有用户数据
可以看到,当用户输入包含单引号等特殊字符时,原本的SQL逻辑会被篡改,这就是SQL注入的典型场景。而参数化查询会把SQL指令和参数值分开处理,数据库会将参数值视为纯数据,不会解析其中的SQL语法,从而彻底避免这类问题。
C#中使用SqlCommand实现参数化查询
如果使用ADO.NET的原生SqlCommand操作SQL Server数据库,参数化查询的实现非常直接,只需要创建SqlParameter对象并添加到命令的参数集合中即可:
using System.Data.SqlClient;
public User GetUserById(int userId)
{
string connectionString = "Server=127.0.0.1;Database=TestDB;User Id=sa;Password=123456;";
string sql = "SELECT Id, UserName, Age FROM Users WHERE Id = @UserId"; // 参数用@前缀标识
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
// 添加参数,指定参数名、类型和值
cmd.Parameters.Add(new SqlParameter("@UserId", System.Data.SqlDbType.Int) { Value = userId });
using (SqlDataReader reader = cmd.ExecuteReader())
{
if (reader.Read())
{
return new User
{
Id = reader.GetInt32(0),
UserName = reader.GetString(1),
Age = reader.GetInt32(2)
};
}
}
}
}
return null;
}
public class User
{
public int Id { get; set; }
public string UserName { get; set; }
public int Age { get; set; }
}注意SQL Server的参数前缀是@,如果是操作MySQL数据库,参数前缀通常是?或者@,使用对应的MySqlCommand时添加参数即可,核心逻辑和SqlCommand一致。
使用Entity Framework时的参数化查询
如果项目中使用Entity Framework作为ORM框架,大部分场景下的查询默认就是参数化的,不需要手动添加参数:
using System.Linq;
public User GetUserByName(string userName)
{
using (var db = new TestDBContext())
{
// LINQ查询默认会生成参数化SQL,避免注入风险
return db.Users.FirstOrDefault(u => u.UserName == userName);
}
}
// 原生SQL查询时也需要使用参数化方式
public User GetUserByRawSql(string userName)
{
using (var db = new TestDBContext())
{
// 原生SQL的参数化写法,MySQL中参数前缀为?,SQL Server为@
string sql = "SELECT * FROM Users WHERE UserName = @UserName";
return db.Users.SqlQuery(sql, new SqlParameter("@UserName", userName)).FirstOrDefault();
}
}需要注意的是,如果在Entity Framework中直接使用字符串拼接构造原生SQL,依然会有注入风险,所以无论使用哪种ORM,只要涉及原生SQL操作,都必须遵循参数化原则。
参数化查询的其他注意事项
- 不要因为参数值来自内部系统就省略参数化步骤,内部输入也可能被篡改,统一使用参数化是最好的习惯
- 不同的数据库参数前缀不同,SQL Server用
@,Oracle用:,MySQL常用?,不要混用 - 参数类型要和数据库字段类型匹配,避免类型转换带来的隐式问题
- 除了查询操作,插入、更新、删除语句同样需要使用参数化方式,不能只关注查询场景
参数化查询不仅能防止SQL注入,还能让数据库缓存执行计划,提升重复执行的SQL的性能,是兼顾安全和性能的优选方案。
总结
在C#开发中,避免SQL注入的核心就是始终使用参数化查询,不管是用原生的SqlCommand还是ORM框架,都要杜绝字符串拼接构造SQL的行为。参数化查询将SQL指令和参数值分离,从数据库层面保证了参数不会被解析为可执行指令,是数据库操作安全编程的基础要求。养成良好的编码习惯,从每一行数据库操作代码做起,才能从根源上规避SQL注入风险。