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

教程3小时前发布 jdkjadmin
0 00
vb.net数据库使用教程:新手完成第一张作品的方法

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 示例适合作为本地单机应用的起点;当数据共享、权限管理或并发需求改变时,也应重新评估数据库方案,而不是只把示例代码扩展得越来越大。

© 版权声明

相关文章