VB.NET数据库增删改查:从连接到执行操作的完整示例

科技资讯3小时前发布 jdkjadmin
0 00
VB.NET数据库增删改查:从连接到执行操作的完整示例

VB.NET数据库增删改查:从连接到执行操作的完整示例

VB.NET 操作数据库,核心流程并不复杂:准备连接字符串,打开连接,创建命令,执行 SQL,再读取或处理结果。真正容易出错的地方,通常是连接配置、参数类型、资源释放以及对数据库返回值的理解。

本文以 SQL Server 为例,使用 Microsoft.Data.SqlClient 和 VB.NET 编写一个可以在控制台项目中运行的增删改查示例。代码采用参数化查询,不依赖 ORM,适合学习 ADO.NET、编写小型工具、维护旧项目,或处理需要直接控制 SQL 的业务逻辑。如果项目使用 SQLite、MySQL 或 PostgreSQL,则应换用对应的数据提供程序;连接类和部分参数类型也会有所不同。

一、准备项目与数据库

示例面向现代 .NET 控制台项目。创建 VB.NET 控制台项目后,通过 NuGet 安装 Microsoft.Data.SqlClient。在 Visual Studio 的 NuGet 包管理器中搜索并安装即可,也可以使用命令行:

dotnet add package Microsoft.Data.SqlClient

本文示例使用名为 CrudDemo 的 SQL Server 数据库。先在 SQL Server 或 LocalDB 中创建数据库,再执行下面的建表脚本:

CREATE DATABASE CrudDemo;
GO

USE CrudDemo;
GO

CREATE TABLE dbo.Products
(
    Id        int IDENTITY(1, 1) NOT NULL
        CONSTRAINT PK_Products PRIMARY KEY,
    Name      nvarchar(100) NOT NULL,
    Price     decimal(10, 2) NOT NULL,
    CreatedAt datetime2 NOT NULL
        CONSTRAINT DF_Products_CreatedAt DEFAULT SYSUTCDATETIME()
);
GO

Id 由数据库自动生成;Name 最长为 100 个 Unicode 字符;Price 使用固定精度小数,适合金额等不应使用二进制浮点数表示的值。CreatedAt 默认记录 UTC 时间。实际项目应根据业务调整表名、字段长度、精度和约束。

二、连接字符串与安全边界

连接字符串描述服务器、数据库以及身份验证方式。以下配置面向安装了 SQL Server LocalDB 的本机开发环境:

Server=(localdb)\MSSQLLocalDB;Database=CrudDemo;Integrated Security=true;Encrypt=true;TrustServerCertificate=true;

生产环境通常使用正式 SQL Server 实例、受控的身份验证方式以及有效的服务器证书。不要把生产数据库密码硬编码在源代码、提交到代码仓库,或直接输出到日志。可以使用环境变量、受保护的配置系统或密钥管理服务提供连接信息。

TrustServerCertificate=true 会跳过对服务器证书的验证,适合某些本地开发环境,但不应为了消除证书错误而不加判断地用于生产连接。生产环境应配置可信且与服务器名称匹配的证书,并正确启用加密。不同 SQL Server 版本和驱动版本的加密默认行为可能不同,应检查实际部署环境的设置。

三、完整的 VB.NET 增删改查代码

下面的代码可以放在控制台项目的 Program.vb 中。请按实际环境修改连接字符串。示例依次插入一条记录、读取列表、更新记录、按编号查询,再删除记录。

Imports System
Imports System.Collections.Generic
Imports Microsoft.Data.SqlClient

Module Program

    Private Const ConnectionString As String =
        "Server=(localdb)\MSSQLLocalDB;" &
        "Database=CrudDemo;" &
        "Integrated Security=true;" &
        "Encrypt=true;" &
        "TrustServerCertificate=true;"

    Sub Main()
        Try
            Dim newId As Integer = AddProduct("键盘", 299.9D)
            Console.WriteLine($"新增记录,Id={newId}")

            Console.WriteLine("当前产品列表:")
            For Each product As Product In GetProducts()
                Console.WriteLine(
                    $"Id={product.Id}, Name={product.Name}, Price={product.Price:F2}")
            Next

            Dim updatedRows As Integer =
                UpdateProduct(newId, "机械键盘", 399.5D)
            Console.WriteLine($"更新影响行数:{updatedRows}")

            Dim found As Product = GetProductById(newId)
            If found IsNot Nothing Then
                Console.WriteLine(
                    $"查询结果:Id={found.Id}, Name={found.Name}, Price={found.Price:F2}")
            End If

            Dim deletedRows As Integer = DeleteProduct(newId)
            Console.WriteLine($"删除影响行数:{deletedRows}")

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

    Private Function AddProduct(name As String, price As Decimal) As Integer
        ValidateProduct(name, price)

        Const sql As String =
            "INSERT INTO dbo.Products (Name, Price) " &
            "OUTPUT INSERTED.Id " &
            "VALUES (@Name, @Price);"

        Using connection As New SqlConnection(ConnectionString)
            Using command As New SqlCommand(sql, connection)
                command.Parameters.Add("@Name", SqlDbType.NVarChar, 100).Value = name
                command.Parameters.Add("@Price", SqlDbType.Decimal).Value = price
                command.Parameters("@Price").Precision = 10
                command.Parameters("@Price").Scale = 2

                connection.Open()
                Dim result As Object = command.ExecuteScalar()
                Return Convert.ToInt32(result)
            End Using
        End Using
    End Function

    Private Function GetProducts() As List(Of Product)
        Const sql As String =
            "SELECT Id, Name, Price FROM dbo.Products ORDER BY Id;"

        Dim products As New List(Of Product)()

        Using connection As New SqlConnection(ConnectionString)
            Using command As New SqlCommand(sql, connection)
                connection.Open()

                Using reader As SqlDataReader = command.ExecuteReader()
                    While reader.Read()
                        products.Add(New Product With {
                            .Id = reader.GetInt32(reader.GetOrdinal("Id")),
                            .Name = reader.GetString(reader.GetOrdinal("Name")),
                            .Price = reader.GetDecimal(reader.GetOrdinal("Price"))
                        })
                    End While
                End Using
            End Using
        End Using

        Return products
    End Function

    Private Function GetProductById(id As Integer) As Product
        Const sql As String =
            "SELECT Id, Name, Price " &
            "FROM dbo.Products WHERE Id = @Id;"

        Using connection As New SqlConnection(ConnectionString)
            Using command As New SqlCommand(sql, connection)
                command.Parameters.Add("@Id", SqlDbType.Int).Value = id

                connection.Open()
                Using reader As SqlDataReader = command.ExecuteReader()
                    If reader.Read() Then
                        Return New Product With {
                            .Id = reader.GetInt32(reader.GetOrdinal("Id")),
                            .Name = reader.GetString(reader.GetOrdinal("Name")),
                            .Price = reader.GetDecimal(reader.GetOrdinal("Price"))
                        }
                    End If
                End Using
            End Using
        End Using

        Return Nothing
    End Function

    Private Function UpdateProduct(
        id As Integer,
        name As String,
        price As Decimal
    ) As Integer
        ValidateProduct(name, price)

        Const sql As String =
            "UPDATE dbo.Products " &
            "SET Name = @Name, Price = @Price " &
            "WHERE Id = @Id;"

        Using connection As New SqlConnection(ConnectionString)
            Using command As New SqlCommand(sql, connection)
                command.Parameters.Add("@Name", SqlDbType.NVarChar, 100).Value = name

                Dim priceParameter =
                    command.Parameters.Add("@Price", SqlDbType.Decimal)
                priceParameter.Precision = 10
                priceParameter.Scale = 2
                priceParameter.Value = price

                command.Parameters.Add("@Id", SqlDbType.Int).Value = id

                connection.Open()
                Return command.ExecuteNonQuery()
            End Using
        End Using
    End Function

    Private Function DeleteProduct(id As Integer) As Integer
        Const sql As String =
            "DELETE FROM dbo.Products WHERE Id = @Id;"

        Using connection As New SqlConnection(ConnectionString)
            Using command As New SqlCommand(sql, connection)
                command.Parameters.Add("@Id", SqlDbType.Int).Value = id

                connection.Open()
                Return command.ExecuteNonQuery()
            End Using
        End Using
    End Function

    Private Sub ValidateProduct(name As String, price As Decimal)
        If String.IsNullOrWhiteSpace(name) Then
            Throw New ArgumentException("产品名称不能为空。", NameOf(name))
        End If

        If name.Length > 100 Then
            Throw New ArgumentException(
                "产品名称不能超过 100 个字符。", NameOf(name))
        End If

        If price < 0D OrElse price > 99999999.99D Then
            Throw New ArgumentException(
                "价格必须在 decimal(10,2) 可表示的范围内。", NameOf(price))
        End If
    End Sub

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

End Module

代码中使用了 SqlDbType,因此如果项目没有自动导入相应命名空间,需要在文件开头保留 Imports System.Data。完整的导入部分应为:

Imports System
Imports System.Collections.Generic
Imports System.Data
Imports Microsoft.Data.SqlClient

四、逐项理解 CRUD 操作

1. 新增:INSERT 与 ExecuteScalar

AddProduct 使用 INSERT 写入名称和价格,并通过 SQL Server 的 OUTPUT INSERTED.Id 取回数据库生成的主键。因为此语句返回一个值,所以使用 ExecuteScalar,而不是 ExecuteNonQuery。后者适用于不需要读取结果集或单个返回值的写操作。

为参数指定 SqlDbType.NVarChar 和长度,能让参数类型与数据库列保持一致。金额参数则显式设置精度和小数位数,避免依赖驱动对参数类型的推断。数据库本身仍应设置合适的数据类型、非空约束和其他业务约束;应用层校验不能替代数据库约束。

2. 查询:ExecuteReader 与读取器生命周期

GetProducts 使用 ExecuteReader 读取多行结果,按列名取得列序号,再使用类型化读取方法取值。列的 SQL 类型必须与读取方法匹配:例如 int 对应 GetInt32,decimal 对应 GetDecimal。

示例表中的 Name 和 Price 都不允许为 NULL,因此可以直接调用 GetString 和 GetDecimal。如果列允许 NULL,应先调用 reader.IsDBNull(ordinal),再决定如何转换。不能把数据库中的 NULL 当成普通字符串或数值直接读取。

SqlDataReader、SqlCommand 和 SqlConnection 都放在 Using 块中。块结束时会释放对应资源,包括发生异常时的清理。ADO.NET 对连接池有支持,因此通常可以按操作创建并释放连接,不必长时间持有一个全局连接对象。

3. 更新:以受限条件避免误改数据

UPDATE 使用 WHERE Id = @Id 指定目标记录。没有正确的筛选条件,更新语句可能影响整张表。ExecuteNonQuery 返回受影响行数:返回 1 通常表示匹配并更新了一行,返回 0 则可能表示编号不存在,也可能表示数据库对受影响行数的报告受到触发器或连接选项等因素影响。实际业务需要根据数据访问场景判断,不能一概把 0 当作数据库故障。

4. 删除:明确删除范围并处理结果

DELETE 同样使用参数限定编号,并返回影响行数。对于批量删除或清理数据,建议先确认筛选条件,并在有需要时使用事务、备份或软删除策略。不要仅凭用户输入拼接出不受限制的删除 SQL。

五、参数化查询能做什么,不能做什么

参数化查询的主要作用,是把 SQL 语句结构与数据值分开处理。@Name、@Price 和 @Id 的值作为参数传给数据库,而不是拼接进 SQL 文本。这样可以正确处理引号等特殊字符,并有效避免将参数值当作 SQL 语句执行的注入风险。

参数只用于值,不能用于表名、列名、关键字或排序方向。比如下面这种写法不能通过把表名当作普通参数来实现:

SELECT * FROM @TableName;

如果表名、列名或排序方向必须动态选择,应从程序内定义的允许列表中映射到固定 SQL 片段,而不是直接接收并拼接任意用户输入。例如只允许排序方式为 ASC 或 DESC,也应在程序中校验并选择相应的固定文本。参数化查询也不能代替权限控制、输入校验或最小权限原则;它解决的是 SQL 值的安全传递,不是全部数据库安全问题。

六、常见错误与排查思路

  • 找不到 SqlClient 类型:确认已安装 Microsoft.Data.SqlClient,并使用正确的 Imports Microsoft.Data.SqlClient。旧项目可能使用 System.Data.SqlClient;不要在同一个文件中混用两个提供程序的类型。
  • 无法连接服务器或数据库:核对服务器实例名、数据库名称、网络连接和身份验证方式。LocalDB 只能用于安装了相应组件的本机环境,不能把示例中的实例名直接当作生产服务器地址。
  • 登录失败或权限不足:确认连接使用的身份有权访问目标数据库,并具备执行所需操作的权限。不要通过授予过宽权限来掩盖配置问题。
  • 参数类型不匹配或字符串截断:检查数据库字段类型、参数类型和长度是否一致。例如 Unicode 字段用 nvarchar,并为字符串参数指定与列相符的长度。
  • decimal 精度或溢出错误:核对列定义、参数的 Precision 和 Scale,以及业务允许的数值范围。金额通常应使用 Decimal,而不是 Double。
  • 读取时出现 NULL 异常:检查列是否允许 NULL;对于可空列先用 IsDBNull 判断,再读取值。
  • 查询结果很多、内存占用过高:示例把全部结果放进列表,适用于演示和结果量较小的场景。大量数据应分页、限制列和行,或在读取器仍打开时逐行处理,避免一次性加载全部记录。
  • 连接或读取资源未释放:将连接、命令和读取器放在 Using 块中,确保成功、失败两种情况下都释放。

七、事务与异步操作的适用场景

如果一项业务需要连续执行多个相互依赖的写操作,应考虑使用数据库事务。只有所有步骤都成功时才提交;任何一步失败时回滚,避免出现只完成一半的状态。单条独立语句通常不需要为了形式而额外包一层事务。使用事务时,应尽量缩短持有时间,并明确处理提交和回滚异常。

桌面程序或 ASP.NET 服务中,如果数据库操作会阻塞界面线程或大量并发请求,可以考虑使用 OpenAsync、ExecuteReaderAsync 等异步 API,并配合异步方法一路传递取消令牌。异步调用主要用于提升等待期间的线程利用效率,不会自动让 SQL 查询更快,也不会替代索引、分页和执行计划优化。示例为便于说明基本流程采用同步 API;在需要异步的项目中,不宜只把单个数据库调用改成异步,而忽略其调用链和资源释放方式。

总结

使用 VB.NET 完成数据库增删改查,可以按固定步骤组织:选择与数据库匹配的数据提供程序,安全配置连接字符串,使用参数化命令传递数据值,通过 ExecuteScalar、ExecuteReader 或 ExecuteNonQuery 执行适合的操作,并用 Using 管理资源。进一步投入生产前,还应结合实际需求设置权限、证书、输入约束、错误记录、事务和分页策略。

© 版权声明

相关文章