vb连接数据库使用教程:新手完成第一张作品的方法

vb连接数据库使用教程:新手完成第一张作品的方法

vb连接数据库使用教程:新手完成第一张作品的方法

第一次让 VB 程序连接数据库,最容易卡住的往往不是 SQL,而是选哪种 VB、用什么数据库,以及连接成功后从哪里开始。本文从一个可运行的小作品出发:用 VB.NET 和 SQLite 保存、查看一份作品清单。做完后,你会掌握连接数据库、创建表、插入数据、读取结果和处理基本错误的流程。

本文中的“VB”指 VB.NET,开发环境为 .NET 8 或兼容的较新 .NET SDK。它不适用于 VB6 的 ADO、ADO.NET 旧式写法,也不应直接套用到 VBA。若你使用 VB6、Excel VBA 或 Access 数据库,需要根据对应平台调整组件和代码。

一、先判断这个方案适不适合你

示例使用 SQLite。它把数据库保存在本地文件中,不需要单独安装和启动数据库服务器,适合学习、桌面工具、个人项目、原型验证,以及访问量不大、主要由一个应用程序使用的数据。数据库文件可以随项目备份,但运行程序的账户必须有权限读取和写入该文件及其所在目录。

如果你的应用要让多台电脑同时访问同一份数据、需要集中管理用户权限、需要远程连接或多人并发写入,通常应评估 SQL Server、PostgreSQL 等服务器数据库。SQLite 虽然支持事务和并发读取,但写入时仍有锁竞争;把 SQLite 文件放在网络共享目录上也不等于获得了可靠的数据库服务器能力。

SQLite 的字段类型和 SQL 行为与 SQL Server 等产品并不完全相同。本文的数据库语句是为 SQLite 编写的,换数据库时,类型、自动编号语法、连接方式和部分 SQL 都需要核对,不能只替换连接字符串。

二、创建项目并安装驱动

先安装 .NET 8 SDK。在命令行中执行以下命令,创建 VB 控制台项目并添加 SQLite 提供程序:

dotnet new console -lang VB -n FirstDatabaseWork
cd FirstDatabaseWork
dotnet add package Microsoft.Data.Sqlite

Microsoft.Data.Sqlite 是微软提供的 SQLite ADO.NET 提供程序。安装完成后,用编辑器打开项目中的 Program.vb,将原有内容替换为下一节的完整代码。第一次运行时,.NET 会还原 NuGet 包。

本文选择控制台,是为了把数据库操作本身讲清楚。后续无论把代码放进 WinForms、WPF 还是其他界面程序,连接、命令和参数化查询的基本原则都相同;界面事件中则要避免执行长时间数据库操作时阻塞界面线程。

三、完成第一个可运行的数据库作品

这个小作品会在程序目录下创建 作品清单.db,建立作品表,插入一条示例记录,再查询并显示全部记录。重复运行会重复插入示例作品,这是为了让初学者能直接看到写入效果;下一节会说明如何避免重复数据。

Imports System
Imports Microsoft.Data.Sqlite

Module Program

    Sub Main()
        Dim databasePath As String =
            IO.Path.Combine(AppContext.BaseDirectory, "作品清单.db")
        Dim connectionString As String =
            "Data Source=" & databasePath

        Try
            Using connection As New SqliteConnection(connectionString)
                connection.Open()

                CreateTable(connection)
                AddWork(connection, "我的第一张作品", "用 VB.NET 连接 SQLite")
                ShowWorks(connection)
            End Using

            Console.WriteLine()
            Console.WriteLine("完成。数据库文件:" & databasePath)
        Catch ex As SqliteException
            Console.WriteLine("数据库操作失败:" & ex.Message)
        Catch ex As Exception
            Console.WriteLine("程序运行失败:" & ex.Message)
        End Try

        Console.WriteLine("按任意键退出。")
        Console.ReadKey()
    End Sub

    Private Sub CreateTable(connection As SqliteConnection)
        Dim sql As String =
            "CREATE TABLE IF NOT EXISTS Works (" &
            "Id INTEGER PRIMARY KEY AUTOINCREMENT, " &
            "Title TEXT NOT NULL, " &
            "Description TEXT NOT NULL, " &
            "CreatedAt TEXT NOT NULL)"

        Using command As New SqliteCommand(sql, connection)
            command.ExecuteNonQuery()
        End Using
    End Sub

    Private Sub AddWork(
        connection As SqliteConnection,
        title As String,
        description As String)

        Dim sql As String =
            "INSERT INTO Works (Title, Description, CreatedAt) " &
            "VALUES ($title, $description, $createdAt)"

        Using command As New SqliteCommand(sql, connection)
            command.Parameters.AddWithValue("$title", title)
            command.Parameters.AddWithValue("$description", description)
            command.Parameters.AddWithValue(
                "$createdAt",
                DateTimeOffset.UtcNow.ToString("O"))

            command.ExecuteNonQuery()
        End Using
    End Sub

    Private Sub ShowWorks(connection As SqliteConnection)
        Dim sql As String =
            "SELECT Id, Title, Description, CreatedAt " &
            "FROM Works ORDER BY Id"

        Using command As New SqliteCommand(sql, connection)
            Using reader As SqliteDataReader = command.ExecuteReader()
                Console.WriteLine("作品清单:")

                While reader.Read()
                    Dim id As Long = reader.GetInt64(0)
                    Dim title As String = reader.GetString(1)
                    Dim description As String = reader.GetString(2)
                    Dim createdAt As String = reader.GetString(3)

                    Console.WriteLine(
                        id & " | " & title & " | " & description)
                    Console.WriteLine("创建时间:" & createdAt)
                End While
            End Using
        End Using
    End Sub

End Module

运行项目:

dotnet run

成功时,控制台会显示一条作品记录,并报告数据库文件路径。AppContext.BaseDirectory 指向程序运行目录;在开发环境中,这通常是项目的构建输出目录,而不一定是项目根目录。部署时应明确数据库文件放在哪里,并确保应用有相应读写权限。

四、读懂代码里的数据库流程

1. 打开连接

SqliteConnection 根据连接字符串定位数据库文件。connection.Open() 真正打开连接;如果文件不存在,SQLite 通常会创建文件,但这并不代表任何路径都可写。目录不存在、权限不足、路径错误等情况仍会导致失败。

Using 结束时会释放连接、命令和读取器,即使中途发生异常也会执行清理。它不是“连接永久常驻”的机制。对于当前小程序,按需打开和释放足够清晰;桌面程序要避免每次按钮操作都创建过多长期未释放的连接。

2. 建表

CREATE TABLE IF NOT EXISTS 表示表不存在时创建,已存在时不重复创建。这便于程序首次运行,但它不会自动把已有表升级到新结构。项目后续增加字段或修改结构时,需要规划数据库迁移;仅修改建表 SQL,不能保证旧数据库随之更新。

Id INTEGER PRIMARY KEY AUTOINCREMENT 定义整数主键并自动分配编号。Title、Description 和 CreatedAt 使用 NOT NULL,表示数据库不接受这些字段为 SQL 的 NULL。这和空字符串不是一回事:空字符串仍是一个值。应用仍应在写入前检查用户输入是否符合业务要求。

SQLite 使用灵活的类型系统,列声明的类型并不等同于 SQL Server 中严格的字段类型约束。上例把时间保存为 ISO 8601 文本,使用 UtcNow 统一保存 UTC 时间,避免不同地区的本地时间混在一起。读取后可以按约定解析;如果你的项目需要按时间筛选和排序,应统一存储格式和时区策略。

3. 写入数据

INSERT INTO 指定要写入的字段,VALUES 中的 $title 等是参数占位符。真实数据通过 command.Parameters 传入,而不是把标题拼接进 SQL 字符串。这样既能正确处理引号等特殊字符,也能降低 SQL 注入风险。

ExecuteNonQuery() 适用于不需要读取结果集的操作,例如建表、插入、更新和删除。它返回受影响行数;如果要确认更新或删除是否命中记录,可以检查返回值。若需要在插入后读取生成的编号,可使用 SQLite 的返回值功能,或另行查询最后插入编号,但应按实际提供程序和数据库版本验证写法。

4. 读取结果

ExecuteReader() 返回一个读取器,Read() 每次移动到下一行。示例按列序号读取数据,因此 SELECT 的列顺序要与读取顺序一致。实际项目也可以按列名取值,以提高代码可读性。若某列允许为 NULL,读取前必须用 IsDBNull 判断,不能直接调用 GetString 或其他非空读取方法。

读取器需要在连接仍然打开时使用,所以示例把读取器的 Using 放在连接内部。按这个资源生命周期组织代码,能减少读取未结束就关闭连接等错误。

五、增加更新和删除功能

作品清单只有新增和查看还不完整。下面的代码可以放进同一个 Module Program 中,作为另外两个私有方法使用。调用时把有效的作品编号传入。更新和删除都使用参数,不要将编号或文本直接拼进 SQL。

Private Sub UpdateWork(
    connection As SqliteConnection,
    id As Long,
    newTitle As String,
    newDescription As String)

    Dim sql As String =
        "UPDATE Works " &
        "SET Title = $title, Description = $description " &
        "WHERE Id = $id"

    Using command As New SqliteCommand(sql, connection)
        command.Parameters.AddWithValue("$title", newTitle)
        command.Parameters.AddWithValue("$description", newDescription)
        command.Parameters.AddWithValue("$id", id)

        Dim affectedRows As Integer = command.ExecuteNonQuery()
        Console.WriteLine("更新行数:" & affectedRows)
    End Using
End Sub

Private Sub DeleteWork(
    connection As SqliteConnection,
    id As Long)

    Dim sql As String = "DELETE FROM Works WHERE Id = $id"

    Using command As New SqliteCommand(sql, connection)
        command.Parameters.AddWithValue("$id", id)

        Dim affectedRows As Integer = command.ExecuteNonQuery()
        Console.WriteLine("删除行数:" & affectedRows)
    End Using
End Sub

WHERE Id = $id 是关键。如果误写成不带 WHERE 的更新或删除语句,可能会修改或删除整张表的数据。真实应用中,应在确认对象和操作范围后执行删除,并考虑备份、撤销或软删除等业务需求。

六、参数化查询的能力与边界

参数可以代替 SQL 中的数据值,例如标题、描述、编号和搜索词。参数会作为值传给数据库,而不是被解释为 SQL 语句的一部分。因此,即使标题内容是 O'Brien,也不必手工替换引号;参数化通常是防止数据值触发 SQL 注入的重要做法。

参数不能代替表名、列名、排序方向或 SQL 关键字。例如,下面的思路并不能靠把表名作为参数实现:

SELECT * FROM $tableName

占位符对应的是值,不是 SQL 结构。若表名、字段名或排序方向必须动态变化,应从程序内固定的白名单中选择,再把选中的标识符按数据库规则安全引用;不要把用户输入直接拼接进 SQL。也不要把参数化误解成对所有输入的业务校验:标题是否为空、是否过长、是否包含不允许的内容,仍需由应用按需求验证。

本文用 AddWithValue 简化示例。它适用于此处这些明确的文本值和整数值;但在不同 ADO.NET 提供程序中,类型推断和转换规则可能不同,不能据此断言它适合所有数据库。对类型转换敏感的场景,应查阅实际提供程序文档,按需要显式设置参数类型,并测试空值、日期及数值范围。

七、可选:让每次运行不重复插入

本文主程序每运行一次就插入一条记录,目的是展示最简单的插入流程。如果希望只初始化一次,可以增加唯一约束,再采用适合业务的冲突处理方式。下面仅展示表结构上的唯一约束思路;它要求作品标题确实应当唯一。若标题本来允许重复,就不应为了去重随意增加这个限制。

CREATE TABLE IF NOT EXISTS Works (
    Id INTEGER PRIMARY KEY AUTOINCREMENT,
    Title TEXT NOT NULL UNIQUE,
    Description TEXT NOT NULL,
    CreatedAt TEXT NOT NULL
)

如果此前已经创建了没有唯一约束的旧表,改动这段建表语句不会自动为旧表补上约束;需要设计迁移,并先处理已有重复数据。不要依靠“先查询有没有,再插入”作为并发环境下的唯一保障,因为两个操作之间可能发生竞争。最终约束应由数据库保证,应用再对冲突作出友好处理。

八、常见错误与排查顺序

1. 找不到命名空间或类型

如果提示找不到 Microsoft.Data.Sqlite,先确认是在正确的项目目录中安装包,并检查项目文件是否包含该依赖。可以执行 dotnet restore 后重新构建;也要确认代码顶部写了 Imports Microsoft.Data.Sqlite。包名和代码中使用的命名空间不要混淆。

2. 数据库文件出现在意料之外的位置

相对路径会受程序当前工作目录影响;示例使用 AppContext.BaseDirectory,因此文件出现在程序运行目录附近。开发环境、发布目录和桌面快捷方式启动时,工作目录可能不同。排查时先输出完整路径,并确认父目录存在、当前账户有写权限。

3. 文件被锁定或写入失败

检查是否有未释放的连接、命令或读取器,多个程序是否同时写入,以及目标文件是否被操作系统或其他软件占用。SQLite 支持事务,但它不是无锁数据库;写入冲突应结合实际错误信息处理,不要简单地无限重试。更高并发或跨机器共享需求,应重新评估数据库架构。

4. 字符串引号导致 SQL 报错

不要通过把用户文本拼进 SQL 来“修复”引号问题。将数据作为参数传入,才是正确处理普通数据值的方式。若 SQL 报错,检查占位符名称是否与参数名称一致,SQL 语句是否拼接完整,以及参数是否绑定在实际执行的命令上。

5. 查询结果为空或读取类型错误

确认程序打开的是你预期的数据库文件,而不是另一个同名文件;再确认插入和查询的表名、字段名一致。查询可能返回零行,这不是数据库连接失败。若字段允许 NULL,先检查空值;读取整数时也要确认数据库中的实际内容可转换为整数。

6. 表结构改了但程序仍报错

CREATE TABLE IF NOT EXISTS 不负责升级已有表。开发阶段可以在确认数据不重要后删除测试数据库文件并重新运行;有真实数据时,不要直接删除文件,应编写迁移或备份后再处理。数据库结构变更应与代码版本一起管理。

九、什么时候需要事务

单条插入语句通常不需要额外手动包一层事务。若一个业务动作包含多条必须一起成功或一起撤销的写入,例如新增作品同时写入多条标签,可以使用事务:开始事务、执行全部命令、成功后提交,发生错误则回滚。不要在事务中等待用户输入或执行很耗时的网络操作,以免长时间占用写入锁。

示例为了保持第一份代码简洁,没有加入多表操作。等你开始实现多步写入时,再明确事务边界,并确保所有相关命令使用同一个连接和事务。事务不能替代输入验证,也不能解决权限、备份或数据迁移问题。

十、从示例走向实际应用

完成后,你已经有了一份能运行的作品清单:数据库由代码初始化,数据通过参数写入,结果通过读取器显示。接下来可以加上用户输入、按编号查询、分页和界面展示;每增加一个功能,都保持几个原则:使用参数传递数据值、检查可能为 NULL 的字段、释放数据库资源,并明确文件路径和数据备份方式。

正式应用还要根据数据敏感程度设计访问控制和备份策略。SQLite 文件本身不等同于具有数据库账户权限隔离的服务器系统;能读取该文件的系统账户通常就可能直接访问其中的数据。参数化查询主要用于安全地传递 SQL 数据值,并不提供文件加密、用户身份验证、授权控制或网络传输保护。需要这些能力时,应选用合适的系统架构和安全措施,而不是把参数化查询当作完整的安全方案。

先让这份最小程序在自己的电脑上运行,再根据目标平台决定是否换成服务器数据库。把数据库选型、文件位置、并发需求和数据安全边界想清楚,比一开始堆很多代码更重要。

© 版权声明

相关文章