
VB数据库编程实例:制作一个简单的学生信息管理程序
学生信息管理是学习 VB 数据库编程的常见练习。它包含了实际程序中反复出现的几个环节:连接数据库、建表、录入数据、查询列表、修改记录和删除记录。本文使用 VB.NET 和 SQLite,制作一个可在命令行运行的简单程序。示例不依赖数据库服务器,适合学习、原型验证和单机小工具;它不是面向多用户、涉密或高并发业务的完整信息系统。
一、适用场景与技术选择
VB 常用于桌面程序。刚接触数据库时,先把数据库操作与界面分开,通常更容易理解:输入和菜单由控制台处理,数据存取由 SQLite 完成。等基本逻辑清楚后,可以把同一套数据访问代码接到 Windows Forms 窗体上。
- VB.NET:运行在 .NET 上,适合编写 Windows 桌面程序,也能通过 .NET SDK 创建控制台应用。
- SQLite:以文件保存数据,不需要单独安装和运行数据库服务器。程序通过 NuGet 包
Microsoft.Data.Sqlite访问数据库。 - 本示例范围:管理学号、姓名、性别、年龄和班级;支持新增、列表、修改和删除。
如果项目已经使用 SQL Server、Access 或其他数据库,不必为了照搬示例而更换数据库。连接方式、SQL 方言、字段类型和事务处理可能不同,应按目标数据库的驱动和文档调整。尤其不能把不同数据库的连接字符串或数据类型定义直接混用。
二、创建项目
安装 .NET SDK 后,在命令行执行以下命令,创建 VB.NET 控制台项目并添加 SQLite 提供程序:
dotnet new console -lang VB -n StudentManager
cd StudentManager
dotnet add package Microsoft.Data.Sqlite
用文本编辑器打开项目里的 Program.vb,将原有内容替换为下方代码,然后运行:
dotnet run
NuGet 包会在编译时由项目还原。程序首次运行时,会在当前操作系统用户的本地应用数据目录下创建 VbStudentDemo 文件夹和 students.db 数据库文件。这样比把数据库固定写入程序安装目录更合适,因为安装目录可能没有写权限。
三、完整示例代码
下面的代码包含建表、输入校验和四项基本操作。程序采用同步数据库调用,适用于这个小型命令行示例;界面较复杂或数据库操作可能耗时的桌面程序,应考虑异步调用,避免阻塞界面线程。
Imports System
Imports System.IO
Imports Microsoft.Data.Sqlite
Module Program
Private ReadOnly DbPath As String =
Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"VbStudentDemo",
"students.db")
Private ReadOnly ConnectionString As String =
"Data Source=" & DbPath
Sub Main()
Try
Directory.CreateDirectory(Path.GetDirectoryName(DbPath))
InitializeDatabase()
Do
Console.WriteLine()
Console.WriteLine("=== 学生信息管理 ===")
Console.WriteLine("1. 新增学生")
Console.WriteLine("2. 查看全部")
Console.WriteLine("3. 修改学生")
Console.WriteLine("4. 删除学生")
Console.WriteLine("0. 退出")
Console.Write("请选择:")
Dim choice As String = Console.ReadLine()
Try
Select Case choice
Case "1"
AddStudent()
Case "2"
ListStudents()
Case "3"
UpdateStudent()
Case "4"
DeleteStudent()
Case "0"
Exit Do
Case Else
Console.WriteLine("无效选项,请重新输入。")
End Select
Catch ex As SqliteException
Console.WriteLine("数据库操作失败:" & ex.Message)
End Try
Loop
Catch ex As Exception
Console.WriteLine("程序初始化失败:" & ex.Message)
End Try
End Sub
Private Sub InitializeDatabase()
Using connection As New SqliteConnection(ConnectionString)
connection.Open()
Dim sql As String =
"CREATE TABLE IF NOT EXISTS Students (" &
"StudentId INTEGER PRIMARY KEY AUTOINCREMENT, " &
"StudentNo TEXT NOT NULL UNIQUE " &
"CHECK(length(trim(StudentNo)) BETWEEN 1 AND 30), " &
"StudentName TEXT NOT NULL " &
"CHECK(length(trim(StudentName)) BETWEEN 1 AND 50), " &
"Gender TEXT NOT NULL " &
"CHECK(length(trim(Gender)) BETWEEN 1 AND 10), " &
"Age INTEGER NOT NULL CHECK(Age BETWEEN 0 AND 150), " &
"ClassName TEXT NOT NULL " &
"CHECK(length(trim(ClassName)) BETWEEN 1 AND 50)" &
");"
Using command As New SqliteCommand(sql, connection)
command.ExecuteNonQuery()
End Using
End Using
End Sub
Private Function ReadRequired(prompt As String, fieldName As String,
maxLength As Integer) As String
Do
Console.Write(prompt)
Dim value As String = Console.ReadLine()
If value Is Nothing Then
value = ""
End If
value = value.Trim()
If value.Length = 0 Then
Console.WriteLine(fieldName & "不能为空。")
ElseIf value.Length > maxLength Then
Console.WriteLine(fieldName & "不能超过 " & maxLength & " 个字符。")
Else
Return value
End If
Loop
End Function
Private Function ReadAge() As Integer
Do
Console.Write("年龄(0-150):")
Dim text As String = Console.ReadLine()
Dim age As Integer
If Integer.TryParse(text, age) AndAlso age >= 0 AndAlso age <= 150 Then
Return age
End If
Console.WriteLine("请输入 0 到 150 之间的整数。")
Loop
End Function
Private Function ReadStudentId() As Long
Do
Console.Write("学生记录编号:")
Dim text As String = Console.ReadLine()
Dim studentId As Long
If Long.TryParse(text, studentId) AndAlso studentId > 0 Then
Return studentId
End If
Console.WriteLine("请输入大于 0 的整数编号。")
Loop
End Function
Private Sub AddStudent()
Dim studentNo As String =
ReadRequired("学号:", "学号", 30)
Dim studentName As String =
ReadRequired("姓名:", "姓名", 50)
Dim gender As String =
ReadRequired("性别:", "性别", 10)
Dim age As Integer = ReadAge()
Dim className As String =
ReadRequired("班级:", "班级", 50)
Using connection As New SqliteConnection(ConnectionString)
connection.Open()
Dim sql As String =
"INSERT INTO Students " &
"(StudentNo, StudentName, Gender, Age, ClassName) " &
"VALUES (@studentNo, @studentName, @gender, @age, @className);"
Using command As New SqliteCommand(sql, connection)
command.Parameters.AddWithValue("@studentNo", studentNo)
command.Parameters.AddWithValue("@studentName", studentName)
command.Parameters.AddWithValue("@gender", gender)
command.Parameters.AddWithValue("@age", age)
command.Parameters.AddWithValue("@className", className)
command.ExecuteNonQuery()
End Using
End Using
Console.WriteLine("新增成功。")
End Sub
Private Sub ListStudents()
Using connection As New SqliteConnection(ConnectionString)
connection.Open()
Dim sql As String =
"SELECT StudentId, StudentNo, StudentName, Gender, Age, ClassName " &
"FROM Students ORDER BY StudentId;"
Using command As New SqliteCommand(sql, connection)
Using reader As SqliteDataReader = command.ExecuteReader()
If Not reader.HasRows Then
Console.WriteLine("当前没有学生记录。")
Return
End If
Console.WriteLine()
Console.WriteLine("编号 | 学号 | 姓名 | 性别 | 年龄 | 班级")
Console.WriteLine("----------------------------------------")
While reader.Read()
Console.WriteLine(
reader.GetInt64(0) & " | " &
reader.GetString(1) & " | " &
reader.GetString(2) & " | " &
reader.GetString(3) & " | " &
reader.GetInt32(4) & " | " &
reader.GetString(5))
End While
End Using
End Using
End Using
End Sub
Private Sub UpdateStudent()
Dim studentId As Long = ReadStudentId()
Dim studentNo As String =
ReadRequired("新学号:", "学号", 30)
Dim studentName As String =
ReadRequired("新姓名:", "姓名", 50)
Dim gender As String =
ReadRequired("新性别:", "性别", 10)
Dim age As Integer = ReadAge()
Dim className As String =
ReadRequired("新班级:", "班级", 50)
Using connection As New SqliteConnection(ConnectionString)
connection.Open()
Dim sql As String =
"UPDATE Students SET StudentNo = @studentNo, " &
"StudentName = @studentName, Gender = @gender, " &
"Age = @age, ClassName = @className " &
"WHERE StudentId = @studentId;"
Using command As New SqliteCommand(sql, connection)
command.Parameters.AddWithValue("@studentNo", studentNo)
command.Parameters.AddWithValue("@studentName", studentName)
command.Parameters.AddWithValue("@gender", gender)
command.Parameters.AddWithValue("@age", age)
command.Parameters.AddWithValue("@className", className)
command.Parameters.AddWithValue("@studentId", studentId)
Dim affected As Integer = command.ExecuteNonQuery()
If affected = 0 Then
Console.WriteLine("没有找到该编号的学生。")
Else
Console.WriteLine("修改成功。")
End If
End Using
End Using
End Sub
Private Sub DeleteStudent()
Dim studentId As Long = ReadStudentId()
Console.Write("确认删除?输入 Y 确认:")
Dim answer As String = Console.ReadLine()
If Not String.Equals(answer, "Y", StringComparison.OrdinalIgnoreCase) Then
Console.WriteLine("已取消。")
Return
End If
Using connection As New SqliteConnection(ConnectionString)
connection.Open()
Dim sql As String =
"DELETE FROM Students WHERE StudentId = @studentId;"
Using command As New SqliteCommand(sql, connection)
command.Parameters.AddWithValue("@studentId", studentId)
Dim affected As Integer = command.ExecuteNonQuery()
If affected = 0 Then
Console.WriteLine("没有找到该编号的学生。")
Else
Console.WriteLine("删除成功。")
End If
End Using
End Using
End Sub
End Module
四、代码中的关键设计
1. 建表语句同时约束数据
StudentId 是自增主键,供程序内部定位记录;StudentNo 设置了 UNIQUE,避免重复学号。姓名、学号等字段标记为 NOT NULL,并通过 SQLite 的 CHECK 约束限制长度或年龄范围。程序在输入阶段也做了对应校验。
应用层校验是为了尽早给用户清楚的反馈;数据库约束则能在数据经由其他代码路径写入时继续保护数据的一致性。两者不能简单互相替代。数据库中的 length 是 SQLite 对字符串长度的计算规则,不应未经验证就当成所有数据库都通用的字符计数方案。
2. 参数化查询保护数据值
新增、修改和删除语句使用 @studentNo、@studentId 等参数,并通过 Parameters.AddWithValue 传入值。这样,学号或姓名里的引号等字符会作为数据处理,而不是拼进 SQL 语句执行,可减少由输入值引起的 SQL 注入风险。
参数化查询只适用于 SQL 中的值,不能把表名、列名或排序方向当作普通参数传入。如果以后要让用户选择排序字段,应从程序预先定义的白名单中选择,再拼接对应的固定标识符;不要直接把用户输入拼进 SQL。参数化也不能代替权限管理、输入校验、访问控制或安全的部署配置。
3. 使用 Using 释放连接和读取器
数据库连接、命令和读取器都放在 Using 代码块中。代码离开代码块时会释放相应资源,即使执行过程中发生异常也一样。每个操作独立打开并关闭连接,适合这种简单的小程序;如果程序需要连接池、事务或更复杂的数据访问层,应该根据实际架构进一步设计。
4. 用执行结果判断更新与删除
ExecuteNonQuery 返回受影响的行数。修改或删除时,如果结果是 0,程序就提示编号不存在;如果大于 0,则报告操作成功。该判断只说明本次 SQL 影响了多少行,不等于业务上的完整成功条件。例如将数据写入多个表时,还需要考虑事务和整体一致性。
五、运行时如何操作
- 运行
dotnet run,菜单出现后输入1,按提示录入一名学生。 - 输入
2查看记录。列表中的编号是数据库主键,后续修改和删除都使用它。 - 输入
3,提供记录编号和新的字段内容,修改学生资料。 - 输入
4,提供编号,并输入Y确认删除。其他输入都会取消删除。 - 输入
0退出。再次运行时,程序会连接到同一个数据库文件,已保存的数据仍在。
如果新增时出现 SQLite 唯一约束错误,常见原因是学号已经存在。当前示例会把底层数据库错误显示出来,便于学习和排查;面向普通用户的正式软件通常应捕获特定约束错误,并提示“该学号已存在”等更清楚的信息,而不是暴露完整的内部异常内容。
六、关键边界与常见错误
把数据库文件放错位置
将数据库保存在程序目录下看似方便,但安装目录可能只读;使用相对路径时,程序还可能因启动目录变化而连接到另一个同名数据库。示例把文件放在当前用户的本地应用数据目录,并用绝对路径连接,避免了这两类常见混淆。调试时可以打印 DbPath 来确认实际文件位置。
以为参数能保护所有 SQL 拼接
如果把用户输入先拼接进 SQL,再把最终 SQL 交给数据库执行,风险仍然存在。正确做法是保留 SQL 模板中的参数占位符,并通过命令参数集合赋值。即便输入来自内部用户,也应保持这种写法,因为输入来源可能变化,且参数化能避免特殊字符导致的语法错误。
忽略唯一约束与异常处理
学号唯一意味着重复学号必然不能插入或修改成功。重复时应向用户解释如何处理,而不是删除唯一约束来“修复”错误。示例在菜单操作附近捕获 SQLite 异常,保证程序可以显示数据库错误;生产程序还应记录适当的诊断信息,同时避免向终端或普通用户展示敏感路径、连接信息等内部细节。
把“删除”误当作可撤销操作
示例执行的是实际的 DELETE,确认后会删除数据库中的记录,并没有回收站或撤销功能。真实业务中可以考虑软删除、审计记录或删除前备份,但这些方案会改变表结构和查询逻辑,需要结合保留期限与合规要求决定。不要假设 SQLite 会自动替应用保留已删除记录。
误以为 SQLite 文件自带访问控制或加密
SQLite 是嵌入式文件数据库。示例没有配置数据库加密,也没有实现数据库账号、角色或行级权限;能够访问该文件的系统用户可能直接读取或复制它。文件访问安全主要依赖操作系统账户和目录权限。若保存身份证号、健康信息等敏感数据,不能仅靠参数化查询就认为数据已经受到充分保护,应评估访问控制、加密、密钥管理、备份保护和适用法规,并选择经过验证的方案。
忽略并发和部署需求
SQLite 很适合单机、小规模、以本地文件为中心的场景,但不意味着它适用于任何用户数量或写入压力。多个实例同时操作、数据库放在网络共享目录、长时间事务或大量并发写入,都可能带来锁等待和运维问题。遇到多用户集中写入、远程访问、复杂权限或高可用需求时,应评估服务器数据库,并采用相应的认证、备份、迁移和监控方案。
只验证界面,不验证数据库约束
当前程序对输入做了基础检查,但例如性别只有长度限制,并未限制为某几个预设选项;这属于示例的业务简化。正式项目应明确字段规则:是否允许未知性别、年龄是否有特殊含义、班级是否需要关联班级表、学号格式是否固定等。不要把示例中的校验范围当成适用于所有学校的统一标准。
七、从控制台扩展到 Windows Forms
迁移到 Windows Forms 时,可以把 AddStudent 等方法放入单独的数据访问类,并将界面控件的内容作为参数传入。点击“新增”按钮时读取文本框并调用新增方法;“查询”按钮可将查询结果绑定到 DataGridView。数据库操作代码仍应使用参数化查询,不应因为输入改成文本框就改用字符串拼接。
建议把职责分成三层:窗体负责展示和收集输入,业务逻辑负责字段校验与业务规则,数据访问层负责 SQL 和数据库连接。这样更容易测试和维护,也便于以后更换数据库。但对小型学习程序,不必为了分层而引入过多框架;先做到连接、命令和界面职责清楚即可。
进一步扩展时,可以加入按学号查询、分页、导入导出、事务、日志和自动备份。每项功能都有自己的边界:例如导入文件需要处理编码、重复数据和部分失败;备份需要考虑运行期间是否有写入以及如何验证备份可恢复。应根据实际需求设计,而不是只增加界面按钮。
八、小结
这份示例实现了一个可运行的 VB.NET 学生信息管理程序:使用 SQLite 保存数据,以主键定位记录,以唯一约束保证学号不重复,以输入检查和数据库约束共同维护数据质量,并通过参数化命令执行新增、修改和删除。
最值得带走的不是某一段 SQL,而是几个开发习惯:确认数据库文件实际位置;用参数传递数据值;释放连接等数据库资源;对唯一约束和不存在的记录给出明确反馈;根据真实使用场景评估并发、备份和敏感数据保护。掌握这些基础后,再接入窗体界面或替换为其他数据库会更稳妥。









