vb.net数据库实战方法:提示词、素材与细节优化

教程19小时前发布 jdkjadmin
0 00
vb.net数据库实战方法:提示词、素材与细节优化

vb.net数据库实战方法:提示词、素材与细节优化

用 VB.NET 写数据库功能,难点往往不在于“怎样执行一条 SQL”,而在于把需求说清楚、选对数据访问方式,并处理好参数、连接、事务和异常。使用 AI 工具生成代码时,这些问题也不会自动消失:提示词缺少数据库类型或运行环境,生成的代码就可能依赖不存在的组件;只要求“写一个登录查询”,则很容易得到无法直接运行、甚至不安全的示例。

本文以 .NET 8 控制台项目和 SQLite 为例,介绍一套可复现的 VB.NET 数据库开发流程,包括如何准备提示词素材、怎样写出可运行的 CRUD 代码,以及如何检查参数化查询、事务和资源释放。示例面向学习、小型桌面应用、原型和本地工具。若你的项目使用 SQL Server、Access 或其他数据库,连接驱动、SQL 方言和部署方式需要相应调整。

一、先判断需求适不适合当前方案

SQLite 是嵌入式关系数据库:数据保存在文件中,应用通过数据库驱动直接读写,不必单独安装和管理数据库服务器。它适合单机工具、桌面应用、本地缓存、离线数据和原型验证。示例中的 Microsoft.Data.Sqlite 是 SQLite 的 ADO.NET 提供程序,使用熟悉的 Connection、Command 和 DataReader 模式。

若多个用户需要通过网络并发访问同一份业务数据、需要集中授权和运维,或必须使用已有企业数据库,应先确认服务器数据库及其驱动,不要因为 SQLite 示例简单就直接套用。SQLite 支持事务,但它不是数据库服务器;并发写入、备份、权限管理和部署边界都需要结合实际需求评估。

  • 适合 SQLite:单机应用、低复杂度本地数据、离线功能、快速验证数据模型。
  • 优先考虑 SQL Server 等服务器数据库:集中式业务系统、多用户写入、统一备份与权限管理、已有数据库基础设施。
  • 不要混淆数据访问层和数据库:ADO.NET 是访问数据库的 API 体系;SQLite、SQL Server 是不同的数据库产品,具体 SQL 与提供程序并不完全相同。

二、让提示词给出可验证的上下文

向 AI 请求数据库代码时,建议把任务拆成“环境、数据结构、行为、约束、验收”五部分。这样既能减少来回补充,也方便人工检查输出是否符合项目。

  • 环境:VB.NET 还是其他语言,目标 .NET 版本,控制台、WinForms 或 ASP.NET,数据库产品与驱动包。
  • 数据结构:表名、列名、类型、主键、唯一约束、可空规则及示例数据。
  • 行为:要新增、查询、更新还是删除;是否分页;找不到记录时怎样处理。
  • 约束:是否允许同步代码、是否必须使用事务、错误如何展示、是否要保留现有接口。
  • 验收:要求完整可编译示例、建表语句、依赖安装方式、成功与失败测试步骤。

可以先用下面这段提示词作为起点,再替换成项目的真实条件:

请用 VB.NET 为 .NET 8 控制台项目编写一个 SQLite 数据访问示例。
数据访问使用 Microsoft.Data.Sqlite,表名为 Products:
Id 为自增整数主键,Name 为必填文本,Price 为必填数值。
需要展示建表、添加商品、按 Id 查询、更新价格和删除记录。
所有外部输入必须通过参数传入;不要把用户输入拼接进 SQL。
请给出 NuGet 安装命令、完整 Program.vb、运行步骤和至少三个验证用例。
代码须说明连接、命令、读取器和事务的释放方式。
不要假设 SQLite 参数可以替代表名或列名;如有相关动态标识符,
请说明应采用固定白名单。

提示词不是安全控制,也不能证明生成代码正确。它的作用是减少歧义、规定输出范围。对于真实项目,还应提供现有代码片段、实体字段定义、目标数据库版本和错误信息。发送素材前要去掉密码、访问令牌、真实个人信息以及不应离开组织的数据。

三、建立可运行的 VB.NET SQLite 项目

创建控制台项目并安装驱动。以下命令适用于已安装 .NET SDK 的环境:

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

将项目中的 Program.vb 替换为下方代码,然后运行 dotnet run。程序会在当前工作目录下创建 products.db,执行建表、插入、读取、更新和删除。示例为便于演示,使用相对路径;桌面应用部署时应选择明确且可写的数据目录,并处理文件权限和备份。

Imports Microsoft.Data.Sqlite

Module Program

    Private Const ConnectionString As String = "Data Source=products.db"

    Sub Main()
        Try
            CreateTable()

            Dim productId As Long = AddProduct("键盘", 299.5D)
            Console.WriteLine($"新增商品 Id:{productId}")

            Dim product As Product = FindProduct(productId)
            If product IsNot Nothing Then
                Console.WriteLine(
                    $"查询结果:{product.Id}, {product.Name}, {product.Price}")
            End If

            UpdatePrice(productId, 279.5D)

            Dim updated As Product = FindProduct(productId)
            If updated IsNot Nothing Then
                Console.WriteLine($"更新后价格:{updated.Price}")
            End If

            DeleteProduct(productId)
            Console.WriteLine("记录已删除。")

        Catch ex As SqliteException
            Console.Error.WriteLine($"数据库错误:{ex.Message}")
        Catch ex As Exception
            Console.Error.WriteLine($"程序错误:{ex.Message}")
        End Try
    End Sub

    Private Sub CreateTable()
        Using connection As New SqliteConnection(ConnectionString)
            connection.Open()

            Using command As SqliteCommand = connection.CreateCommand()
                command.CommandText =
                    "CREATE TABLE IF NOT EXISTS Products (" &
                    "Id INTEGER PRIMARY KEY AUTOINCREMENT, " &
                    "Name TEXT NOT NULL, " &
                    "Price NUMERIC NOT NULL CHECK (Price >= 0)" &
                    ");"
                command.ExecuteNonQuery()
            End Using
        End Using
    End Sub

    Private Function AddProduct(name As String, price As Decimal) As Long
        If String.IsNullOrWhiteSpace(name) Then
            Throw New ArgumentException("商品名称不能为空。", NameOf(name))
        End If

        If price < 0D Then
            Throw New ArgumentOutOfRangeException(NameOf(price))
        End If

        Using connection As New SqliteConnection(ConnectionString)
            connection.Open()

            Using command As SqliteCommand = connection.CreateCommand()
                command.CommandText =
                    "INSERT INTO Products (Name, Price) VALUES ($name, $price);"
                command.Parameters.AddWithValue("$name", name)
                command.Parameters.AddWithValue("$price", price)
                command.ExecuteNonQuery()

                command.CommandText = "SELECT last_insert_rowid();"
                command.Parameters.Clear()

                Return CLng(command.ExecuteScalar())
            End Using
        End Using
    End Function

    Private Function FindProduct(id As Long) As Product
        Using connection As New SqliteConnection(ConnectionString)
            connection.Open()

            Using command As SqliteCommand = connection.CreateCommand()
                command.CommandText =
                    "SELECT Id, Name, Price FROM Products WHERE Id = $id;"
                command.Parameters.AddWithValue("$id", id)

                Using reader As SqliteDataReader = command.ExecuteReader()
                    If Not reader.Read() Then
                        Return Nothing
                    End If

                    Return New Product With {
                        .Id = reader.GetInt64(0),
                        .Name = reader.GetString(1),
                        .Price = Convert.ToDecimal(reader.GetValue(2))
                    }
                End Using
            End Using
        End Using
    End Function

    Private Sub UpdatePrice(id As Long, price As Decimal)
        If price < 0D Then
            Throw New ArgumentOutOfRangeException(NameOf(price))
        End If

        Using connection As New SqliteConnection(ConnectionString)
            connection.Open()

            Using command As SqliteCommand = connection.CreateCommand()
                command.CommandText =
                    "UPDATE Products SET Price = $price WHERE Id = $id;"
                command.Parameters.AddWithValue("$price", price)
                command.Parameters.AddWithValue("$id", id)

                Dim affected As Integer = command.ExecuteNonQuery()
                If affected = 0 Then
                    Console.WriteLine($"没有找到 Id 为 {id} 的商品。")
                End If
            End Using
        End Using
    End Sub

    Private Sub DeleteProduct(id As Long)
        Using connection As New SqliteConnection(ConnectionString)
            connection.Open()

            Using command As SqliteCommand = connection.CreateCommand()
                command.CommandText = "DELETE FROM Products WHERE Id = $id;"
                command.Parameters.AddWithValue("$id", id)
                command.ExecuteNonQuery()
            End Using
        End Using
    End Sub

    Private Class Product
        Public Property Id As Long
        Public Property Name As String
        Public Property Price As Decimal
    End Class

End Module

四、读懂示例中的关键做法

1. 使用参数传值,而不是拼接 SQL

下面的写法把输入直接拼进 SQL,不应在业务代码中采用:

' 错误示例:不要将外部输入直接拼接为 SQL
command.CommandText =
    "SELECT Id, Name FROM Products WHERE Name = '" & name & "';"

当输入包含引号时,拼接可能导致语法错误;若输入被当作 SQL 语句的一部分,还可能产生 SQL 注入风险。正确做法是把 SQL 结构与数据值分开:

command.CommandText =
    "SELECT Id, Name FROM Products WHERE Name = $name;"
command.Parameters.AddWithValue("$name", name)

参数化查询的安全用途,是让数据库提供程序将参数作为值处理,而不是把值解释为 SQL 代码。它能有效防止这类由数据值拼接造成的注入问题,但不是“整个程序自动安全”的保证。仍需验证业务输入、控制数据库文件及账号的访问权限、谨慎处理错误信息,并审查动态 SQL 和其他外部命令调用。

2. 参数不能替代表名、列名或 SQL 关键字

参数适合传入名称、编号、日期等数据值。下面这种写法不能用参数动态指定表名:

' 错误思路:表名不是数据值,不能靠参数替换
command.CommandText = "SELECT * FROM $tableName;"
command.Parameters.AddWithValue("$tableName", tableName)

如果确实需要根据选项切换表或排序列,应从程序预先定义的固定白名单中选择标识符,再拼入 SQL 结构;用户输入不得原样当作标识符使用。例如,可将界面选项映射为程序内固定的 Id 或 Name,并拒绝列表之外的值。白名单不能用来替代普通数据参数:条件值仍应使用参数传入。

3. Using 管理资源生命周期

数据库连接、命令和读取器都实现了释放机制。示例用嵌套 Using 确保方法正常返回或抛出异常时,相关资源仍会释放。不要只在程序结束时才关闭连接,也不要在读取器尚未关闭时随意复用同一连接执行其他命令。

每个方法打开并释放自己的连接,适合这个小型示例,也便于理解生命周期。较大的应用通常会将数据库操作放入独立的数据访问层,统一管理连接创建、映射、日志和错误处理;这不意味着应长期持有一个全局打开的连接。提供程序通常会通过连接池复用底层资源,具体行为取决于驱动和配置。

4. 检查查询结果与受影响行数

ExecuteNonQuery 常用于执行插入、更新、删除或建表语句,返回受影响行数;不同语句和提供程序的计数语义可能有所差异,不要把任何返回值都解释成“操作成功”。更新或删除按主键执行时,可以检查返回值是否为零,以区分“找不到记录”和“确实修改了记录”。

ExecuteScalar 用于取得单个值;ExecuteReader 用于读取多列或多行结果。示例在读取前检查 Read(),避免默认假设记录一定存在。生产代码还要明确处理空值:若某列允许 NULL,应使用 reader.IsDBNull(序号) 检查后再读取,不能直接调用非空类型读取方法。

五、事务:把多步写入作为一个整体

若一项业务需要连续修改多行数据,例如扣减库存并新增订单,任意一步失败时通常不希望只保存一部分结果。此时可以将相关命令放入同一事务中。下面的函数演示向两张表写入一条订单及其明细;运行它之前,需要创建对应表。

Private Sub CreateOrder(productId As Long, quantity As Integer)
    If quantity <= 0 Then
        Throw New ArgumentOutOfRangeException(NameOf(quantity))
    End If

    Using connection As New SqliteConnection(ConnectionString)
        connection.Open()

        Using transaction As SqliteTransaction = connection.BeginTransaction()
            Try
                Using orderCommand As SqliteCommand = connection.CreateCommand()
                    orderCommand.Transaction = transaction
                    orderCommand.CommandText =
                        "INSERT INTO Orders (CreatedAt) VALUES ($createdAt);"
                    orderCommand.Parameters.AddWithValue(
                        "$createdAt", DateTime.UtcNow.ToString("O"))
                    orderCommand.ExecuteNonQuery()
                End Using

                Dim orderId As Long
                Using idCommand As SqliteCommand = connection.CreateCommand()
                    idCommand.Transaction = transaction
                    idCommand.CommandText = "SELECT last_insert_rowid();"
                    orderId = CLng(idCommand.ExecuteScalar())
                End Using

                Using itemCommand As SqliteCommand = connection.CreateCommand()
                    itemCommand.Transaction = transaction
                    itemCommand.CommandText =
                        "INSERT INTO OrderItems (OrderId, ProductId, Quantity) " &
                        "VALUES ($orderId, $productId, $quantity);"
                    itemCommand.Parameters.AddWithValue("$orderId", orderId)
                    itemCommand.Parameters.AddWithValue("$productId", productId)
                    itemCommand.Parameters.AddWithValue("$quantity", quantity)
                    itemCommand.ExecuteNonQuery()
                End Using

                transaction.Commit()
            Catch
                transaction.Rollback()
                Throw
            End Try
        End Using
    End Using
End Sub

需要配套的最简表结构如下:

CREATE TABLE IF NOT EXISTS Orders (
    Id INTEGER PRIMARY KEY AUTOINCREMENT,
    CreatedAt TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS OrderItems (
    Id INTEGER PRIMARY KEY AUTOINCREMENT,
    OrderId INTEGER NOT NULL,
    ProductId INTEGER NOT NULL,
    Quantity INTEGER NOT NULL CHECK (Quantity > 0)
);

事务示例展示的是基本模式,并不等于完整的订单业务。实际实现还需要确认商品是否存在、库存是否充足、外键约束是否启用、金额如何计算,以及并发修改的规则。事务只能保证其覆盖范围内的数据操作具有相应的原子性,不能替代业务校验或权限控制。若回滚本身也可能失败,需根据应用的错误处理策略记录原始异常与回滚异常。

六、模糊查询、排序与输入校验

参数化不意味着任意输入都符合业务规则。例如价格是负数、名称为空、编号超出允许范围,都需要由应用验证;数据库中的 NOT NULL、CHECK、主键和唯一约束则能从数据层补充约束,避免其他写入路径绕过应用校验。

执行包含通配符的查询时,参数仍可安全传值,但要区分“参数值安全”和“搜索语义正确”。在 SQLite 中,LIKE 通常把 % 和 _ 当作通配符;如果产品要求按用户输入进行字面匹配,需要额外设计转义规则,并在 SQL 中配套指定转义字符。参数化不会自动把通配符变成普通字符。

Private Function SearchProducts(keyword As String) As List(Of Product)
    Dim results As New List(Of Product)()

    Using connection As New SqliteConnection(ConnectionString)
        connection.Open()

        Using command As SqliteCommand = connection.CreateCommand()
            command.CommandText =
                "SELECT Id, Name, Price FROM Products " &
                "WHERE Name LIKE $pattern ORDER BY Name;"
            command.Parameters.AddWithValue("$pattern", "%" & keyword & "%")

            Using reader As SqliteDataReader = command.ExecuteReader()
                While reader.Read()
                    results.Add(New Product With {
                        .Id = reader.GetInt64(0),
                        .Name = reader.GetString(1),
                        .Price = Convert.ToDecimal(reader.GetValue(2))
                    })
                End While
            End Using
        End Using
    End Using

    Return results
End Function

这个查询将关键字包在百分号之间,因此会按“包含”进行匹配;它只展示常见语义,并未实现通配符字面转义。传入的百分号和下划线仍可能影响匹配范围。数据量较大时,前置通配符也可能让常规索引无法有效用于查找,应结合数据规模、查询计划和业务需求评估全文检索等方案。

七、参数类型、精度与数据库差异

示例使用 AddWithValue 简化代码。它会根据传入的 .NET 值推断参数类型,适合简单演示,但在跨数据库项目或类型要求严格的场景,应显式设置参数类型,并按提供程序规则处理长度、精度和小数位。不能假设不同 ADO.NET 提供程序对参数类型推断、日期映射或小数存储的行为完全一致。

金额示例使用 Decimal,适合表达十进制业务数值;但 SQLite 的类型系统和存储规则与 SQL Server 并不相同。若要求严格的货币精度,可评估以最小货币单位整数存储,并在应用中明确换算规则;同时决定舍入方式和币种精度。不要仅依靠某个数据库列声明,就假设所有数据库都以相同方式执行固定小数精度。

SQLite 中的 INTEGER、TEXT、NUMERIC 等声明,与 SQL Server 的 BIGINT、NVARCHAR、DECIMAL 不是简单的一一替换。切换数据库时,除了改连接字符串和安装 NuGet 包,还要检查 SQL 语法、主键生成方式、日期时间、参数命名、事务行为和数据库迁移脚本。

八、常见错误与排查顺序

  • 找不到 Microsoft.Data.Sqlite:确认已在正确项目目录执行 NuGet 安装命令,并检查项目文件中是否出现相应包引用。
  • 数据库文件生成在意外位置:相对路径相对于进程的当前工作目录,不一定是源代码目录。调试和发布环境可能有不同工作目录,必要时改用明确计算的数据目录。
  • 表不存在或列名拼错:检查建表是否实际执行、连接字符串是否指向同一数据库文件,以及 SQL 中表名和列名是否一致。
  • 找不到记录却直接读取:先检查 Read() 返回值;按主键更新或删除时也要检查受影响行数。
  • 参数名不匹配:核对 SQL 中的参数标记与添加参数时使用的名称,避免缺少参数或名称重复导致错误。
  • 把参数当作表名:参数绑定的是值,不是 SQL 结构。动态标识符应映射到固定白名单。
  • 忘记处理空值:数据库允许 NULL 时,读取代码须先判断是否为空;写入时要明确传入数据库空值还是有效值。
  • 每条语句都单独执行却期待整体回滚:多步操作需要在同一连接和同一事务中执行,并确保每条命令关联该事务。
  • 吞掉异常后继续运行:不要用空的 Catch 隐藏失败。应记录必要上下文、向调用方传递失败状态,并避免在日志中暴露密码或敏感数据。
  • 将 SQL Server 示例直接复制到 SQLite:不同数据库的驱动、语法、类型和功能存在差异,应针对目标数据库重新验证。

另一个常见误区是把异步方法等同于更快的数据库访问。是否适合异步,取决于提供程序和具体 I/O 实现。Microsoft.Data.Sqlite 的异步 ADO.NET 方法在当前实现中不提供真正的异步 SQLite I/O,相关调用可能以同步方式执行;它们不应被理解为自动获得并发性能提升。若 UI 线程需要保持响应,应结合驱动能力、任务调度和应用架构设计,而不是只把方法名改成 Async。

九、用测试用例检查生成代码

AI 生成的示例要经过最小验证,不能因为能编译就认为业务正确。至少检查以下情况:

  1. 全新目录启动程序,确认数据库和表能够创建。
  2. 插入正常记录,再按返回的主键查询,确认字段值正确。
  3. 传入空名称、负价格和不存在的主键,确认程序按预期处理。
  4. 使用包含单引号的名称,例如 Sam's keyboard,验证参数化写入和查询正常。
  5. 在执行事务的第二步故意制造错误,确认第一步写入也被回滚。
  6. 尝试重复插入应唯一的业务编号,确认数据库约束与应用提示符合需求。

测试数据应使用虚构内容;如果测试数据库包含真实业务数据,也要遵守组织的权限、脱敏和保存策略。排查问题时,可以提供经过脱敏的异常文本、最小表结构和可复现步骤,但不要把连接密码或生产数据直接粘贴到外部工具中。

十、从示例走向可维护项目

示例把数据库代码放在一个模块中,目的是缩短上手路径。业务代码增多后,可将职责拆分为模型、数据访问服务和界面逻辑:界面负责收集输入与呈现结果,数据访问层负责执行 SQL 并返回数据,业务层负责业务规则。这样更容易测试和替换数据库实现,但拆分本身不能消除 SQL 注入、错误映射或事务设计问题。

提示词也可随项目演进。不要只要求“优化代码”,而要指出具体目标,例如“保留现有公有接口,将重复连接创建抽取到仓储类”“增加分页,不要把用户输入拼接到 ORDER BY”“为新增、更新和删除补充事务边界及失败测试”。同时要求列出改动点、假设、依赖版本和未覆盖情况。对生成的迁移脚本和删除语句尤其要谨慎:先在测试数据库验证,并备份不可轻易重建的数据。

实用的数据库开发流程可以归纳为:先确定数据库和运行环境,再提供准确的表结构与业务规则;生成最小代码后检查参数、类型、资源释放和事务;最后以异常输入、空结果、约束冲突和回滚场景进行验证。AI 能帮助整理样板代码、解释错误和补充测试,但数据库边界、数据安全与上线风险仍由项目开发者负责判断。

© 版权声明

相关文章