VB Visual Basic 2026 中文教程
第 27 课 · 安全地把数据传给 SQL

参数化 SQL 查询

数据库应用绝不能把用户输入直接拼进 SQL 字符串。更可靠的做法是把 SQL 命令结构与数据值分开:SQL 中使用 @Name、@Course、@Score 等参数占位符,再通过 SqlParameter 明确指定数据类型、长度和实际值。本课将把前两课的 CRUD 进一步升级成更安全、更可维护的写法。

环境:Visual Studio 2026 · VB.NET · .NET 10数据库:StudentManagementDB实作:SecureStudentCrud2026

本课学习目标

  • 理解为什么不能直接拼接用户输入到 SQL。
  • 理解参数化 SQL 的基本思想。
  • 认识 SQL 参数名称,例如 @Name
  • 使用 command.Parameters.Add() 建立 SqlParameter。
  • 使用 SqlDbType 指定数据库类型。
  • 为 NVARCHAR 参数指定长度。
  • 为 DECIMAL 参数指定 Precision 与 Scale。
  • 在 INSERT、UPDATE、DELETE 和 SELECT 中使用参数。
  • 理解参数化 SQL 如何降低 SQL 注入风险。
  • 避免依赖字符串格式、引号和区域设置拼接 SQL。
  • 完成 SecureStudentCrud2026。

27.1 最危险的习惯:直接拼 SQL

初学者很容易把 TextBox.Text 与 SQL 字符串直接连接起来。这样的代码虽然看起来简单,却会带来安全、引号、数据类型和维护问题。

不推荐:字符串拼接

SQL 结构和用户输入混在同一个字符串里,程序无法清楚区分“命令”和“数据”。

推荐:参数化查询

SQL 命令保持固定结构,用户输入只作为参数值传给数据库驱动。

不推荐示例

Dim sql As String =
    "SELECT * FROM dbo.Students " &
    "WHERE Name = '" &
    txtName.Text &
    "';"
不要在真实项目中使用这种写法 用户输入可能包含引号或恶意 SQL 片段;即使没有安全攻击,普通姓名中的特殊字符也可能令 SQL 语句失效。

27.2 参数化 SQL 的基本思想

先写固定 SQL:

SELECT
    StudentID,
    Name,
    Course,
    Score
FROM dbo.Students
WHERE Name = @Name;

然后在 VB.NET 中另外给:

@Name

一个真正的数据值。

用户输入李明
SQL 参数@Name
SqlParameter类型 + 值
SqlCommand固定 SQL
数据库执行查询

27.3 第一个 SqlParameter

command.Parameters.Add(
    "@Name",
    SqlDbType.NVarChar,
    100).Value =
    studentName

这一行同时说明:

@Name
SQL 中的参数名称。
SqlDbType.NVarChar
数据库参数类型。
100
NVARCHAR 最大长度。
studentName
真正传入的 VB.NET 值。

27.4 为什么要明确指定 SqlDbType?

数据库已经知道 Students 表的字段定义:

Name   NVARCHAR(100)
Course NVARCHAR(100)
Score  DECIMAL(5,2)

因此参数也应该尽可能与数据库字段类型匹配。

SqlDbType.NVarChar

适合 Name、Course 等 Unicode 字符串。

SqlDbType.Int

适合 StudentID。

SqlDbType.Decimal

适合 Score。

SqlDbType.DateTime2

适合以后可能加入的日期时间字段。

基本规则:参数的数据类型与数据库列类型尽量一致。

27.5 DECIMAL 参数要注意 Precision 与 Scale

Students.Score 定义为:

DECIMAL(5,2)

可以这样建立参数:

Dim scoreParameter As SqlParameter =
    command.Parameters.Add(
        "@Score",
        SqlDbType.Decimal)

scoreParameter.Precision = 5
scoreParameter.Scale = 2
scoreParameter.Value = score

这里:

Precision = 5
总共最多 5 位数字。
Scale = 2
小数点后最多 2 位。

27.6 安全 INSERT

SQL:

INSERT INTO dbo.Students
    (Name, Course, Score)
VALUES
    (@Name, @Course, @Score);

完整方法:

Private Function InsertStudent(
    studentName As String,
    course As String,
    score As Decimal) As Integer

    Const sql As String =
        "INSERT INTO dbo.Students " &
        "(Name, Course, Score) " &
        "VALUES (@Name, @Course, @Score);"

    Using connection As New SqlConnection(
        DatabaseHelper.ConnectionString)

        Using command As New SqlCommand(
            sql,
            connection)

            command.Parameters.Add(
                "@Name",
                SqlDbType.NVarChar,
                100).Value =
                studentName

            command.Parameters.Add(
                "@Course",
                SqlDbType.NVarChar,
                100).Value =
                course

            Dim scoreParameter As SqlParameter =
                command.Parameters.Add(
                    "@Score",
                    SqlDbType.Decimal)

            scoreParameter.Precision = 5
            scoreParameter.Scale = 2
            scoreParameter.Value = score

            connection.Open()

            Return command.ExecuteNonQuery()

        End Using

    End Using

End Function

27.7 安全 SELECT:按 StudentID 查询

Private Function FindStudentById(
    studentId As Integer) As DataTable

    Const sql As String =
        "SELECT StudentID, Name, Course, Score " &
        "FROM dbo.Students " &
        "WHERE StudentID = @StudentID;"

    Using connection As New SqlConnection(
        DatabaseHelper.ConnectionString)

        Using command As New SqlCommand(
            sql,
            connection)

            command.Parameters.Add(
                "@StudentID",
                SqlDbType.Int).Value =
                studentId

            Using adapter As New SqlDataAdapter(
                command)

                Dim table As New DataTable()

                adapter.Fill(table)

                Return table

            End Using

        End Using

    End Using

End Function

27.8 安全 SELECT:按姓名查询

Private Function FindStudentsByName(
    studentName As String) As DataTable

    Const sql As String =
        "SELECT StudentID, Name, Course, Score " &
        "FROM dbo.Students " &
        "WHERE Name = @Name " &
        "ORDER BY StudentID;"

    Using connection As New SqlConnection(
        DatabaseHelper.ConnectionString)

        Using command As New SqlCommand(
            sql,
            connection)

            command.Parameters.Add(
                "@Name",
                SqlDbType.NVarChar,
                100).Value =
                studentName

            Using adapter As New SqlDataAdapter(
                command)

                Dim table As New DataTable()

                adapter.Fill(table)

                Return table

            End Using

        End Using

    End Using

End Function
精确查询与模糊搜索不同 本课重点是参数化查询本身,因此先使用 WHERE Name = @Name。Lesson 28 会进一步讲 LIKE、筛选与排序。

27.9 安全 UPDATE

Private Function UpdateStudent(
    studentId As Integer,
    studentName As String,
    course As String,
    score As Decimal) As Integer

    Const sql As String =
        "UPDATE dbo.Students " &
        "SET Name = @Name, " &
        "Course = @Course, " &
        "Score = @Score " &
        "WHERE StudentID = @StudentID;"

    Using connection As New SqlConnection(
        DatabaseHelper.ConnectionString)

        Using command As New SqlCommand(
            sql,
            connection)

            command.Parameters.Add(
                "@StudentID",
                SqlDbType.Int).Value =
                studentId

            command.Parameters.Add(
                "@Name",
                SqlDbType.NVarChar,
                100).Value =
                studentName

            command.Parameters.Add(
                "@Course",
                SqlDbType.NVarChar,
                100).Value =
                course

            Dim scoreParameter As SqlParameter =
                command.Parameters.Add(
                    "@Score",
                    SqlDbType.Decimal)

            scoreParameter.Precision = 5
            scoreParameter.Scale = 2
            scoreParameter.Value = score

            connection.Open()

            Return command.ExecuteNonQuery()

        End Using

    End Using

End Function

27.10 安全 DELETE

Private Function DeleteStudent(
    studentId As Integer) As Integer

    Const sql As String =
        "DELETE FROM dbo.Students " &
        "WHERE StudentID = @StudentID;"

    Using connection As New SqlConnection(
        DatabaseHelper.ConnectionString)

        Using command As New SqlCommand(
            sql,
            connection)

            command.Parameters.Add(
                "@StudentID",
                SqlDbType.Int).Value =
                studentId

            connection.Open()

            Return command.ExecuteNonQuery()

        End Using

    End Using

End Function

27.11 参数化 SQL 为什么更安全?

核心原则是:

字符串拼接

数据库收到的是“SQL 命令 + 用户输入混合后的一个大字符串”。

参数化 SQL

数据库驱动分别收到固定 SQL 结构和参数值,用户数据不会被当成 SQL 结构解析。

因此参数化 SQL 能明显降低 SQL 注入风险,也减少处理引号、数字小数点格式和日期格式时的错误。

27.12 AddWithValue 与 Add 的区别

前一课为了让代码容易入门,我们曾经使用:

command.Parameters.AddWithValue(
    "@Name",
    studentName)

它会根据传入值推断参数类型。

本课改成:

command.Parameters.Add(
    "@Name",
    SqlDbType.NVarChar,
    100).Value =
    studentName
AddWithValue

代码短、入门容易,但数据库类型和长度由提供程序根据值推断。

Add + SqlDbType

类型和长度明确,更容易与表结构保持一致,也更适合作为长期项目习惯。

27.13 数据库 NULL 与 DBNull.Value

如果以后某个数据库字段允许 NULL,不能简单把 VB 的 Nothing 当作数据库 NULL。

数据库参数通常使用:

DBNull.Value

例如:

Dim value As Object

If String.IsNullOrWhiteSpace(
    txtNotes.Text) Then

    value = DBNull.Value

Else

    value = txtNotes.Text.Trim()

End If

command.Parameters.Add(
    "@Notes",
    SqlDbType.NVarChar,
    500).Value =
    value
当前 Students 表没有可空字段 所以本项目暂时不需要 DBNull.Value;这里先建立概念,为未来更真实的表设计做准备。

27.14 实作:Secure Student CRUD 2026

建立新的 Windows Forms 项目:

SecureStudentCrud2026

继续使用上一课的 DataGridView 界面,并新增一个“按 ID 查询”按钮:

Secure Student CRUD 2026
Student ID:
2
姓名:
王芳
课程:
Visual Basic 2026
成绩:
82
新增 按 ID 查询 显示全部 修改 删除
Student ID姓名课程成绩
2王芳Visual Basic 202682.00

27.15 按 ID 查询按钮

Private Sub btnFindById_Click(
    sender As Object,
    e As EventArgs) Handles btnFindById.Click

    Dim studentId As Integer

    If Not Integer.TryParse(
        txtStudentID.Text,
        studentId) Then

        MessageBox.Show(
            "请输入有效 Student ID。")

        Exit Sub

    End If

    Try

        Dim table As DataTable =
            FindStudentById(
                studentId)

        dgvStudents.DataSource =
            table

        If table.Rows.Count = 0 Then

            lblStatus.Text =
                "找不到这个 Student ID。"

        Else

            lblStatus.Text =
                "查询完成。"

        End If

    Catch ex As SqlException

        MessageBox.Show(
            ex.Message,
            "数据库错误",
            MessageBoxButtons.OK,
            MessageBoxIcon.Error)

    End Try

End Sub

27.16 显示全部仍然不需要参数

并不是每一条 SQL 都必须有参数。如果查询没有任何用户输入:

SELECT
    StudentID,
    Name,
    Course,
    Score
FROM dbo.Students
ORDER BY StudentID;

就没有参数需要传入。

重点:当 SQL 需要使用外部数据值时,用参数传值;不要为了“形式上参数化”而制造没有意义的参数。

27.17 参数命名习惯

数据库字段建议参数
StudentID@StudentID
Name@Name
Course@Course
Score@Score

名称清楚时,SQL 与 VB.NET 参数设置更容易互相对应。

27.18 参数化查询常见错误

错误 1

SQL 有 @Name,但没有加入参数

SqlCommand 执行时会发现参数缺失。

错误 2

参数名称拼错

SQL 的 @StudentID 与代码中的参数名称必须对应。

错误 3

类型与数据库列不一致

例如把 StudentID 当成 NVarChar,会增加不必要的转换和错误风险。

错误 4

只参数化部分用户输入

所有来自用户或外部来源、作为 SQL 数据值使用的内容都应该通过参数传入。

27.19 安全 CRUD 的完整流程

1
验证用户输入

先检查空白、数字和范围。

2
写固定 SQL

SQL 中使用 @Name、@Score 等参数。

3
建立 SqlParameter

指定名称、SqlDbType、长度或 Precision / Scale。

4
执行 SqlCommand

ExecuteNonQuery、Fill 或 ExecuteReader。

5
刷新界面

重新 LoadStudents,让 DataGridView 显示最新数据。

27.20 小练习

  1. 把 Lesson 26 中所有 AddWithValue 改成 Add + SqlDbType。
  2. 建立按课程精确查询:WHERE Course = @Course
  3. 建立按最低成绩查询:WHERE Score >= @MinScore
  4. 为 @MinScore 设置 SqlDbType.Decimal、Precision = 5、Scale = 2。
  5. 故意把参数名称从 @StudentID 拼错一次,观察错误信息,然后修正。
  6. 设计一个允许 NULL 的 Notes 字段,并写出 DBNull.Value 的参数处理逻辑。
  7. 解释为什么参数化查询仍然不能取代输入验证。

本课复习

  1. 为什么不应该把 TextBox.Text 直接连接到 SQL 字符串?
  2. SQL 中的 @Name 是什么?
  3. SqlParameter 的作用是什么?
  4. 为什么要指定 SqlDbType?
  5. NVARCHAR 参数为什么应该指定长度?
  6. DECIMAL 参数中的 Precision 与 Scale 分别表示什么?
  7. AddWithValue 与 Add + SqlDbType 有什么区别?
  8. 参数化 SQL 如何降低 SQL 注入风险?
  9. 什么时候需要使用 DBNull.Value?
  10. 为什么没有用户输入的 SELECT 不一定需要参数?