
vb.net数据库使用教程:新手完成第一张作品的方法
做一个能保存作品名称和作者的小程序,是学习 VB.NET 数据库操作的好起点。本文将从新建项目开始,使用 SQLite 建立本地数据库,创建作品表,并完成插入和查询。示例可以直接运行,也会说明参数化查询的用途、它的边界,以及数据库初学者常遇到的问题。
一、先确定适用场景
数据库不是一种单一产品。选什么数据库,取决于程序运行在哪里、数据由谁共享,以及应用需要承受多大的访问量。
- SQLite:数据保存在一个本地文件里,不需要单独安装和运行数据库服务器。适合桌面工具、学习项目、原型、小型本地应用,以及单机使用的资料管理程序。
- SQL Server、PostgreSQL 等服务型数据库:需要连接数据库服务器,适合多用户共享数据、集中管理和部署在服务器上的应用。
- Access 等文件型数据库:在特定办公环境中也可能合适,但需要考虑驱动、版本和部署条件。
下面使用 SQLite。它易于上手,但不代表所有数据库都适合它。若多个用户需要同时写入同一份数据、程序需要集中权限管理,或有可靠性与运维要求,应评估服务型数据库,而不是把 SQLite 文件放在共享文件夹里就当作服务器数据库使用。
二、准备项目和数据库驱动
先安装 .NET SDK。在命令行中创建 VB.NET 控制台项目,再添加 Microsoft.Data.Sqlite NuGet 包:
dotnet new console --language VB -n WorksDemo
cd WorksDemo
dotnet add package Microsoft.Data.Sqlite
这是 .NET 应用使用 SQLite 的常见方式。NuGet 包会提供所需的托管 API;实际运行时还依赖对应平台的原生 SQLite 组件,通常由包依赖一并处理。若目标平台特殊,发布前仍应在目标环境测试。
打开项目中的 Program.vb,用下面代码替换文件内容。程序会在当前 Windows 用户的本地应用数据目录建立 VBWorksDemo/works.db,创建作品表,插入一条示例记录,再读取并显示记录。每次运行都会再插入一条,因此多运行几次会看到重复示例;后面会说明如何调整。
三、创建表、插入作品并查询
Imports System
Imports System.IO
Imports Microsoft.Data.Sqlite
Module Program
Sub Main()
Dim dataDirectory As String =
Path.Combine(
Environment.GetFolderPath(
Environment.SpecialFolder.LocalApplicationData),
"VBWorksDemo")
Directory.CreateDirectory(dataDirectory)
Dim databasePath As String =
Path.Combine(dataDirectory, "works.db")
Dim connectionString As String =
$"Data Source={databasePath}"
Using connection As New SqliteConnection(connectionString)
connection.Open()
Dim createTableSql As String =
"CREATE TABLE IF NOT EXISTS Works (" &
"Id INTEGER PRIMARY KEY AUTOINCREMENT, " &
"Title TEXT NOT NULL, " &
"Author TEXT NOT NULL, " &
"CreatedAt TEXT NOT NULL)"
Using createCommand As New SqliteCommand(createTableSql, connection)
createCommand.ExecuteNonQuery()
End Using
Dim insertSql As String =
"INSERT INTO Works (Title, Author, CreatedAt) " &
"VALUES (@title, @author, @createdAt)"
Using insertCommand As New SqliteCommand(insertSql, connection)
insertCommand.Parameters.Add("@title", SqliteType.Text).Value =
"我的第一件作品"
insertCommand.Parameters.Add("@author", SqliteType.Text).Value =
"小林"
insertCommand.Parameters.Add("@createdAt", SqliteType.Text).Value =
DateTime.UtcNow.ToString("O")
Dim affectedRows As Integer = insertCommand.ExecuteNonQuery()
Console.WriteLine($"新增记录数:{affectedRows}")
End Using
Dim selectSql As String =
"SELECT Id, Title, Author, CreatedAt " &
"FROM Works ORDER BY Id"
Using selectCommand As New SqliteCommand(selectSql, connection)
Using reader As SqliteDataReader = selectCommand.ExecuteReader()
Console.WriteLine()
Console.WriteLine("作品列表:")
While reader.Read()
Dim id As Long = reader.GetInt64(0)
Dim title As String = reader.GetString(1)
Dim author As String = reader.GetString(2)
Dim createdAt As String = reader.GetString(3)
Console.WriteLine(
$"{id}. {title} — {author}({createdAt})")
End While
End Using
End Using
End Using
Console.WriteLine()
Console.WriteLine("数据库文件:")
Console.WriteLine(databasePath)
End Sub
End Module
运行项目:
dotnet run
第一次运行时,程序会创建数据库文件和 Works 表,然后插入记录。以后再次运行,CREATE TABLE IF NOT EXISTS 会保留已有表,不会重新建表;插入语句则会新增一条记录。数据库不会因为关闭程序而消失,文件路径会在程序输出中显示。
四、读懂这段数据库代码
1. 连接对象负责打开数据库
SqliteConnection 表示与 SQLite 数据库的连接。连接字符串中的 Data Source 指定数据库文件路径。调用 Open 后才能通过该连接执行命令。代码使用 Using 管理连接和命令,在离开代码块时释放资源;这是数据库代码中很重要的习惯。
本例没有把数据库放进程序安装目录,是因为安装目录可能没有写入权限。将数据放进当前用户的本地应用数据目录,更适合这个单机示例。真实应用还应规划备份、数据迁移和卸载时是否保留数据。
2. 建表语句描述数据结构
Works 表有四列:
Id是整数主键,INTEGER PRIMARY KEY AUTOINCREMENT让 SQLite 自动分配递增编号。Title和Author使用文本类型,并通过NOT NULL限制不能保存 SQL 的NULL值。CreatedAt保存创建时间。本例把 UTC 时间按 ISO 8601 格式写成文本,便于查看和解析。
NOT NULL 只阻止数据库值为 NULL,不等于禁止空字符串。若作品名称不能留空,界面验证和数据库约束都应按实际规则设计。SQLite 对列类型的处理也不同于一些严格类型系统;不要仅凭声明了 TEXT 就假设输入内容一定符合业务格式。
3. 插入时使用参数
插入语句中的 @title、@author 和 @createdAt 是命名参数。随后通过 Parameters.Add 给参数指定文本类型并设置值。SQL 结构与用户输入因此分开处理,标题里含有单引号等字符时也不需要自行拼接、转义 SQL 字符串。
例如,标题是 Bob's sketch 时,仍然可以原样作为参数值传入。不要把标题直接拼进 SQL,例如把用户输入拼在 VALUES ('...') 里面;这种做法容易产生语法错误,也可能形成 SQL 注入漏洞。
4. 查询用读取器逐行读取
ExecuteReader 用于执行返回结果集的查询,reader.Read() 每次移动到下一行。示例按查询列的顺序,用 GetInt64 和 GetString 读取字段。列的顺序和类型要与读取方式对应;若查询结果可能包含 NULL,应先使用 IsDBNull 判断,不能直接调用字符串读取方法。
插入、更新和删除通常使用 ExecuteNonQuery。它返回受影响的行数,可用于判断操作是否作用到预期记录。查询结果集则通常使用 ExecuteReader;需要读取单个汇总值时,可以考虑 ExecuteScalar。
五、补上修改和删除操作
数据库程序通常不止新增和列表。下面是参数化更新语句的写法,可放在连接已经打开的位置运行。它只修改编号为 1 的记录;若该编号不存在,受影响行数为零。
Dim updateSql As String =
"UPDATE Works SET Title = @title WHERE Id = @id"
Using updateCommand As New SqliteCommand(updateSql, connection)
updateCommand.Parameters.Add("@title", SqliteType.Text).Value =
"修改后的作品名称"
updateCommand.Parameters.Add("@id", SqliteType.Integer).Value = 1L
Dim affectedRows As Integer = updateCommand.ExecuteNonQuery()
Console.WriteLine($"更新记录数:{affectedRows}")
End Using
删除也应使用明确条件和参数。下面只删除编号为 1 的作品:
Dim deleteSql As String =
"DELETE FROM Works WHERE Id = @id"
Using deleteCommand As New SqliteCommand(deleteSql, connection)
deleteCommand.Parameters.Add("@id", SqliteType.Integer).Value = 1L
Dim affectedRows As Integer = deleteCommand.ExecuteNonQuery()
Console.WriteLine($"删除记录数:{affectedRows}")
End Using
不要轻易运行没有 WHERE 条件的 UPDATE 或 DELETE:它们可能影响整张表。实际程序可在执行前确认用户选择的记录,并对意外的影响行数作出提示。
六、参数化查询的安全用途和边界
参数化查询的核心作用,是将数据值作为参数传递,而不是让输入内容变成 SQL 语法的一部分。它可以降低由用户输入造成的 SQL 注入风险,也能妥善处理引号等特殊字符。它不是整个应用的安全方案:身份验证、授权、文件访问控制、错误信息处理和备份保护仍需分别考虑。
参数通常用于值,例如作品名称、编号、日期等。它不能用来代替表名、列名或 SQL 关键字。下面这种写法并不能让表名按预期工作:
SELECT * FROM @tableName
如果需要动态选择表名或排序方向,应从程序控制的固定白名单中选择合法标识符,再构造 SQL;不能直接把用户输入拼接为表名或排序表达式。参数化也不会自动验证业务规则:例如参数不会替你判断名称是否为空、作品是否属于当前登录用户,或用户是否有权修改该行。
参数值的类型应与数据含义一致。示例使用 SqliteType.Text 和 SqliteType.Integer 明确指定类型。AddWithValue 也常见,但它需要根据 .NET 值推断类型,初学时明确写类型更容易看清数据如何传递。需要传递 SQL 空值时,应使用 DBNull.Value,而不是把 VB 的 Nothing 当成所有场景下等价的数据库 NULL。
七、参数限制不是一个固定数字
不要把某个固定的“参数最大数量”当成所有 SQLite 程序都通用的上限。SQLite 对单条 SQL 语句的变量编号有上限,这个上限与 SQLite 版本及编译配置有关;具体应用使用的 SQLite 构建也可能影响可用范围。实际项目若要执行大量参数,应查询目标运行时的限制,并避免把极长的参数列表当作常规设计。
例如,一次给数万条记录逐个绑定参数,可能遇到变量数量限制,也会使 SQL 语句变大。常见做法是分批处理,或使用事务包裹多条参数化插入。事务可以把一组相关操作作为整体提交或回滚,减少部分写入造成的不一致:
Using transaction As SqliteTransaction = connection.BeginTransaction()
Try
Using command As New SqliteCommand(
"INSERT INTO Works (Title, Author, CreatedAt) " &
"VALUES (@title, @author, @createdAt)",
connection,
transaction)
command.Parameters.Add("@title", SqliteType.Text)
command.Parameters.Add("@author", SqliteType.Text)
command.Parameters.Add("@createdAt", SqliteType.Text)
For i As Integer = 1 To 3
command.Parameters("@title").Value = $"作品 {i}"
command.Parameters("@author").Value = "小林"
command.Parameters("@createdAt").Value =
DateTime.UtcNow.ToString("O")
command.ExecuteNonQuery()
Next
End Using
transaction.Commit()
Catch
transaction.Rollback()
Throw
End Try
End Using
批处理示例复用一个命令和参数定义,只更新每次循环的值。三条记录都成功时提交;出现异常时回滚并继续抛出异常,让调用方知道操作失败。生产代码还应根据应用的错误处理策略记录日志,并确保回滚时连接和事务仍处于可用状态。
八、常见错误与排查方法
1. 找不到程序集或命名空间
确认项目目录正确,并成功执行了 dotnet add package Microsoft.Data.Sqlite。包添加后,重新运行 dotnet restore 或在 IDE 中还原 NuGet 包。若在不能访问网络的环境中构建,也要确认依赖包已事先缓存或配置好包源。
2. 程序运行了,却找不到数据库文件
SQLite 文件路径如果是相对路径,会相对于进程的工作目录,而不一定是项目目录。示例使用绝对路径,并在结尾输出数据库文件位置。排查时先看这条输出,不要只在项目文件夹里寻找数据库。若应用运行在服务器或容器中,还要确认数据目录具有写入权限且持久化方式符合部署要求。
3. 每次运行都多出一条相同记录
示例为了演示插入,故意在每次启动时新增作品。真实程序通常应由用户点击“新增”时才执行插入。若某个业务字段必须唯一,可在数据库结构中添加唯一约束,并处理违反约束时的异常;只在界面里检查重复值无法可靠防止并发操作造成的重复。
4. 查询不到结果或读取时报错
确认查询的表名、列名和筛选条件与数据库一致。若字段允许 NULL,读取前先用 reader.IsDBNull(ordinal) 判断。读取器中的序号从零开始,并按 SELECT 的列顺序对应,不是表中列的原始编号。
5. 字符串拼接导致 SQL 错误或安全问题
不要手工给用户输入加引号,也不要通过替换单引号来“修复”拼接。把值改为参数传递。需要动态列名时,使用应用内部的白名单映射,而不是把任意输入直接写入 SQL。
6. 多条操作只成功了一部分
若一组操作在业务上必须共同成功,应使用事务。单条 SQL 的成功不等于整个业务流程成功;例如新增作品后还要新增标签关系,第二步失败时可能留下不完整数据。事务边界应覆盖这些相关操作,提交前检查必要条件。
九、把示例变成自己的作品管理程序
从控制台示例升级时,可以把数据库操作放进单独的类或服务中,再由界面调用;不要在按钮事件里塞进大量 SQL 和连接管理逻辑。桌面程序运行耗时操作时,应留意界面线程阻塞,按应用类型选择异步 API 或后台任务。还应根据需要增加作品分类、创建时间、备注等字段,并为常用查询设计索引。
正式使用前至少检查以下事项:数据库文件存放位置是否可写;是否有定期备份方案;删除和修改是否有明确目标;错误时是否能提示用户又不泄露不必要的内部细节;多用户访问时所选数据库是否合适;发布到目标机器后原生依赖是否正常加载。
结语
完成第一张数据库作品,关键不在于记住很多 API,而在于掌握一条完整路径:选择适合场景的数据库,打开连接,建表,用参数传值,执行命令,读取结果,并妥善释放资源。本文的 SQLite 示例适合作为本地单机应用的起点;当数据共享、权限管理或并发需求改变时,也应重新评估数据库方案,而不是只把示例代码扩展得越来越大。




