本课学习目标
- 理解为什么不能直接拼接用户输入到 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 &
"';"
27.2 参数化 SQL 的基本思想
先写固定 SQL:
SELECT
StudentID,
Name,
Course,
Score
FROM dbo.Students
WHERE Name = @Name;
然后在 VB.NET 中另外给:
@Name
一个真正的数据值。
27.3 第一个 SqlParameter
command.Parameters.Add(
"@Name",
SqlDbType.NVarChar,
100).Value =
studentName
这一行同时说明:
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
这里:
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
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 注入风险,也减少处理引号、数字小数点格式和日期格式时的错误。
27.12 AddWithValue 与 Add 的区别
前一课为了让代码容易入门,我们曾经使用:
command.Parameters.AddWithValue(
"@Name",
studentName)
它会根据传入值推断参数类型。
本课改成:
command.Parameters.Add(
"@Name",
SqlDbType.NVarChar,
100).Value =
studentName
代码短、入门容易,但数据库类型和长度由提供程序根据值推断。
类型和长度明确,更容易与表结构保持一致,也更适合作为长期项目习惯。
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
27.14 实作:Secure Student CRUD 2026
建立新的 Windows Forms 项目:
SecureStudentCrud2026
继续使用上一课的 DataGridView 界面,并新增一个“按 ID 查询”按钮:
| Student ID | 姓名 | 课程 | 成绩 |
|---|---|---|---|
| 2 | 王芳 | Visual Basic 2026 | 82.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;
就没有参数需要传入。
27.17 参数命名习惯
| 数据库字段 | 建议参数 |
|---|---|
| StudentID | @StudentID |
| Name | @Name |
| Course | @Course |
| Score | @Score |
名称清楚时,SQL 与 VB.NET 参数设置更容易互相对应。
27.18 参数化查询常见错误
SQL 有 @Name,但没有加入参数
SqlCommand 执行时会发现参数缺失。
参数名称拼错
SQL 的 @StudentID 与代码中的参数名称必须对应。
类型与数据库列不一致
例如把 StudentID 当成 NVarChar,会增加不必要的转换和错误风险。
只参数化部分用户输入
所有来自用户或外部来源、作为 SQL 数据值使用的内容都应该通过参数传入。
27.19 安全 CRUD 的完整流程
先检查空白、数字和范围。
SQL 中使用 @Name、@Score 等参数。
指定名称、SqlDbType、长度或 Precision / Scale。
ExecuteNonQuery、Fill 或 ExecuteReader。
重新 LoadStudents,让 DataGridView 显示最新数据。
27.20 小练习
- 把 Lesson 26 中所有 AddWithValue 改成 Add + SqlDbType。
- 建立按课程精确查询:WHERE Course = @Course。
- 建立按最低成绩查询:WHERE Score >= @MinScore。
- 为 @MinScore 设置 SqlDbType.Decimal、Precision = 5、Scale = 2。
- 故意把参数名称从 @StudentID 拼错一次,观察错误信息,然后修正。
- 设计一个允许 NULL 的 Notes 字段,并写出 DBNull.Value 的参数处理逻辑。
- 解释为什么参数化查询仍然不能取代输入验证。
本课复习
- 为什么不应该把 TextBox.Text 直接连接到 SQL 字符串?
- SQL 中的 @Name 是什么?
- SqlParameter 的作用是什么?
- 为什么要指定 SqlDbType?
- NVARCHAR 参数为什么应该指定长度?
- DECIMAL 参数中的 Precision 与 Scale 分别表示什么?
- AddWithValue 与 Add + SqlDbType 有什么区别?
- 参数化 SQL 如何降低 SQL 注入风险?
- 什么时候需要使用 DBNull.Value?
- 为什么没有用户输入的 SELECT 不一定需要参数?