SQL参数化中SqlParameter如何正确使用?

99ANYc3cd6
预计阅读时长 24 分钟
位置: 首页 参数 正文

什么是 SQL 参数化查询?

SQL 参数化查询是一种将 SQL 语句的代码部分(结构)和数据部分(值)分开处理的技术。

sql参数化 sqlparameter
(图片来源网络,侵删)
  1. 先定义一个 SQL 查询的“模板”或“骨架”,其中不确定的数据值用占位符(如 @parameterName)代替。
  2. 通过参数对象(如 SqlParameter)将具体的值安全地传递给这个模板。

举个例子,对比一下非参数化和参数化查询:

非参数化查询 (不推荐 - 存在 SQL 注入风险)

// 危险!用户输入直接拼接到SQL字符串中
string userName = "admin'; DROP TABLE Users; --";
string sql = "SELECT * FROM Users WHERE Username = '" + userName + "' AND Password = 'somepass'";

userName 是恶意输入,admin'; DROP TABLE Users; --,最终执行的 SQL 语句就变成了: SELECT * FROM Users WHERE Username = 'admin'; DROP TABLE Users; --' AND Password = 'somepass' 数据库会先执行查询,然后执行 DROP TABLE Users,导致灾难性后果,这就是 SQL 注入


参数化查询 (推荐 - 安全且高效)

sql参数化 sqlparameter
(图片来源网络,侵删)
// 安全!SQL 语句和数据是分离的
string userName = "admin'; DROP TABLE Users; --";
string sql = "SELECT * FROM Users WHERE Username = @username AND Password = @password";
// 使用 SqlParameter 对象来传递值
SqlParameter[] parameters = new SqlParameter[]
{
    new SqlParameter("@username", userName),
    new SqlParameter("@password", "somepass")
};

在这种情况下,数据库引擎会先解析 SELECT * FROM Users WHERE Username = @username AND Password = @password 这个语句的结构,它会把 @username 这个占位符的值当作一个纯粹的字符串来处理,而不会去执行其中的分号或SQL命令,SQL 注入被完全阻止了。


为什么必须使用参数化查询?(核心优势)

防止 SQL 注入攻击 (最重要的原因)

这是参数化查询最核心、最不可替代的价值,如上所述,它将数据和代码分离,确保用户输入永远不会被解释为 SQL 命令的一部分,数据库引擎只会将参数值作为数据处理,从而从根本上杜绝了 SQL 注入的可能性。

提高性能

对于频繁执行的 SQL 语句,数据库可以缓存其执行计划

  • 非参数化查询"WHERE Name = 'Alice'""WHERE Name = 'Bob'" 被数据库视为两个完全不同的 SQL 语句,因此需要分别解析和生成执行计划。
  • 参数化查询"WHERE Name = @name" 无论 @name 的值是什么,都只被视为一个 SQL 语句,数据库可以缓存这个通用语句的执行计划,当再次执行时,只需将新的参数值填入即可,大大减少了解析和编译的开销。

数据类型安全和代码可读性

  • 类型安全SqlParameter 允许你明确指定参数的数据类型(如 SqlDbType.Int, SqlDbType.NVarChar),这可以防止因数据类型不匹配导致的错误,并让代码意图更清晰。
  • 可读性:使用 @username 这样的命名参数,比用问号 (在某些数据库中)或者手动拼接字符串要清晰得多,代码更易于维护。

SqlParameter 详解

SqlParameter 是 ADO.NET 中用于表示 SQL 查询参数的类,它位于 System.Data.SqlClient 命名空间下。

sql参数化 sqlparameter
(图片来源网络,侵删)

常用属性

属性名 类型 描述
ParameterName string 参数的名称,必须与 SQL 语句中的占位符匹配(如 @username)。
Value object 参数的值,可以是基本类型(int, string, bool)或 DBNull
SqlDbType SqlDbType 指定参数的 SQL Server 数据类型(如 Int, NVarChar, DateTime),虽然通常可以不设,但显式设置是最佳实践。
Size int 对于字符串类型(NVarChar, VarChar),指定其最大长度,对于 XmlUdt 类型,指定大小。
Direction ParameterDirection 指定参数是输入、输出、输入输出还是返回值,默认为 Input
IsNullable bool 指示参数值是否可以为 null
Precision byte 对于数值类型,指定总位数。
Scale byte 对于数值类型,指定小数位数。

常用方法

主要使用 SqlParameter 的构造函数来创建实例。


SqlParameter 的使用示例

下面我们通过 ADO.NET 的经典步骤(连接、命令、执行、读取)来看 SqlParameter 的具体应用。

示例 1:查询数据

using System;
using System.Data;
using System.Data.SqlClient;
public class UserRepository
{
    private readonly string _connectionString = "Your_Connection_String_Here";
    public User GetUserById(int userId)
    {
        User user = null;
        string sql = "SELECT Id, Username, Email FROM Users WHERE Id = @userId";
        // 使用 using 语句确保资源被正确释放
        using (SqlConnection connection = new SqlConnection(_connectionString))
        {
            // 创建 SqlCommand 对象
            using (SqlCommand command = new SqlCommand(sql, connection))
            {
                // 添加参数
                command.Parameters.AddWithValue("@userId", userId);
                try
                {
                    connection.Open();
                    using (SqlDataReader reader = command.ExecuteReader())
                    {
                        if (reader.Read())
                        {
                            user = new User
                            {
                                Id = reader.GetInt32(0),
                                Username = reader.GetString(1),
                                Email = reader.GetString(2)
                            };
                        }
                    }
                }
                catch (Exception ex)
                {
                    // 处理异常
                    Console.WriteLine($"An error occurred: {ex.Message}");
                }
            }
        }
        return user;
    }
}
public class User
{
    public int Id { get; set; }
    public string Username { get; set; }
    public string Email { get; set; }
}

代码解析:

  1. SqlCommand command = new SqlCommand(sql, connection);:创建命令对象,SQL 模板中使用了 @userId 占位符。
  2. command.Parameters.AddWithValue("@userId", userId);:这是最简单添加参数的方式,它会自动根据 userId 变量的类型推断 SqlParameterSqlDbType
  3. ExecuteReader 执行时,@userId 会被安全地替换为传入的 userId 值。

示例 2:插入数据

public int AddUser(string username, string email)
{
    string sql = "INSERT INTO Users (Username, Email) VALUES (@username, @email); SELECT SCOPE_IDENTITY();";
    int newId = -1;
    using (SqlConnection connection = new SqlConnection(_connectionString))
    {
        using (SqlCommand command = new SqlCommand(sql, connection))
        {
            // 添加参数
            command.Parameters.Add(new SqlParameter("@username", SqlDbType.NVarChar, 50));
            command.Parameters["@username"].Value = username;
            command.Parameters.AddWithValue("@email", email);
            try
            {
                connection.Open();
                // ExecuteScalar 用于执行查询并返回结果的第一行第一列
                // SCOPE_IDENTITY() 会返回新插入行的 ID
                object result = command.ExecuteScalar();
                if (result != null && result != DBNull.Value)
                {
                    newId = Convert.ToInt32(result);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"An error occurred: {ex.Message}");
            }
        }
    }
    return newId;
}

代码解析:

  • 这里展示了两种添加参数的方式:AddWithValuenew SqlParameter
  • SCOPE_IDENTITY() 是一个常用的 SQL 函数,用于获取当前会话中最后插入的标识值(自增ID)。
  • ExecuteScalar() 适合执行这种只返回单个值的查询。

示例 3:使用输出参数

有时我们需要从存储过程或SQL语句中获取返回值。

public void GetUserNameAndCount(int userId, out string userName, out int totalUsers)
{
    userName = null;
    totalUsers = -1;
    string sql = "SELECT @userNameOut = Username FROM Users WHERE Id = @userId; SELECT @totalUsersOut = COUNT(*) FROM Users;";
    using (SqlConnection connection = new SqlConnection(_connectionString))
    {
        using (SqlCommand command = new SqlCommand(sql, connection))
        {
            // 输入参数
            command.Parameters.AddWithValue("@userId", userId);
-- 展开阅读全文 --
头像
华为荣耀畅玩note高配版参数具体有哪些?
« 上一篇 11-29
Fitbit Surge智能手表值得买吗?
下一篇 » 11-29

相关文章

取消
微信二维码
支付宝二维码

最近发表

标签列表

目录[+]