VB连接数据库的基本步骤与常见写法

VB连接数据库的基本步骤与常见写法

VB连接数据库的基本步骤与常见写法

“VB 连接数据库”可能指两种差异很大的开发环境:一种是 VB.NET,通常通过 ADO.NET 访问数据库;另一种是经典 VB6,常通过 ADO(ActiveX Data Objects)访问数据库。它们使用的库、对象模型和部署方式都不相同,代码不能直接互换。本文先介绍更适合新项目的 VB.NET,再说明 VB6 中常见的 ADO 写法。

示例以 SQL Server 为主。理解连接、命令、参数、读取结果和释放资源这几个环节后,替换数据库驱动与连接字符串,也能应用到其他数据库。数据库版本、驱动版本和项目目标框架会影响具体 API,正式使用前应核对相应驱动的文档。

一、先确认开发环境与数据库

开始写代码前,先确认以下信息:

  • 使用哪种 VB:VB.NET 项目使用 .NET 类库;VB6 项目使用 COM 组件与 ADO。若正在维护 VB6 程序,不要把 VB.NET 的 Using、SqlConnection 等代码直接贴进去。
  • 使用哪种数据库:例如 SQL Server、Access、MySQL、PostgreSQL 或 SQLite。每种数据库需要对应的驱动或数据提供程序。
  • 数据库如何连接:本机、局域网、云服务,还是通过应用服务器访问。网络策略、防火墙、TLS 和认证方式都会影响连接是否成功。
  • 应用需要什么权限:日常业务程序通常只需访问特定数据库、表或存储过程,不应使用数据库管理员账号。

如果是新建 VB.NET 项目,推荐使用当前受支持的 .NET 版本和数据库提供程序。下文 SQL Server 示例使用 Microsoft.Data.SqlClient。在 Visual Studio 中可通过 NuGet 安装该包;也可以在项目目录执行:

dotnet add package Microsoft.Data.SqlClient

如果项目有既定依赖,也可能使用 System.Data.SqlClient。两者 API 相似,但属于不同的程序集和维护路线,代码与包不要随意混搭。旧版 .NET Framework 项目还应确认目标框架是否支持所选版本的驱动。

二、连接数据库的基本步骤

一个典型的数据库读写过程可以拆成六步:

  1. 准备并核对连接字符串,确定服务器、数据库和认证方式。
  2. 创建连接对象,但不要在程序启动时长期占用一个连接。
  3. 打开连接,捕获并记录有用的异常信息。
  4. 创建命令,明确 SQL、参数和命令类型。
  5. 执行查询或更新,读取并校验返回结果。
  6. 释放读取器、命令和连接;需要原子更新时使用事务。

ADO.NET 的连接对象通常由连接池管理。每次操作按需创建并及时释放连接,并不意味着每次都会重新建立一条物理网络连接;连接池会在适用时复用连接。相反,长时间持有连接会占用池中的资源,也容易在异常路径中造成泄漏。

三、准备示例表

以下 SQL Server 脚本可在测试数据库中运行。请先连接到自己有权限使用的数据库,再执行:

CREATE TABLE dbo.Contacts
(
    ContactId INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
    FullName NVARCHAR(100) NOT NULL,
    Email NVARCHAR(254) NULL,
    CreatedAt DATETIME2 NOT NULL
        CONSTRAINT DF_Contacts_CreatedAt DEFAULT SYSUTCDATETIME()
);

不要在生产数据库中未经评估就执行建表或删表语句。生产环境通常应使用经过审核的数据库迁移脚本,并做好备份与回滚方案。

四、VB.NET:插入数据并返回新记录编号

下面是一个可运行的控制台程序示例。将连接字符串中的服务器和数据库名改成自己的值,并确保当前身份有权访问数据库。示例使用 Windows 集成认证,不把数据库密码写在代码中。

Imports System
Imports System.Data
Imports Microsoft.Data.SqlClient

Module Program
    Sub Main()
        Dim connectionString As String =
            "Server=localhost;Database=VbDemo;" &
            "Integrated Security=True;Encrypt=True;" &
            "TrustServerCertificate=False;"

        Try
            Dim newId As Integer =
                AddContact(connectionString, "林青", "[email protected]")

            Console.WriteLine("新增记录编号:" & newId)
        Catch ex As SqlException
            Console.Error.WriteLine("数据库操作失败:" & ex.Message)
        Catch ex As Exception
            Console.Error.WriteLine("程序运行失败:" & ex.Message)
        End Try
    End Sub

    Private Function AddContact(
        connectionString As String,
        fullName As String,
        email As String
    ) As Integer

        Const sql As String =
            "INSERT INTO dbo.Contacts (FullName, Email) " &
            "OUTPUT INSERTED.ContactId " &
            "VALUES (@FullName, @Email);"

        Using connection As New SqlConnection(connectionString)
            Using command As New SqlCommand(sql, connection)
                command.Parameters.Add("@FullName", SqlDbType.NVarChar, 100).Value =
                    fullName

                If email Is Nothing Then
                    command.Parameters.Add("@Email", SqlDbType.NVarChar, 254).Value =
                        DBNull.Value
                Else
                    command.Parameters.Add("@Email", SqlDbType.NVarChar, 254).Value =
                        email
                End If

                connection.Open()
                Dim result As Object = command.ExecuteScalar()

                If result Is Nothing OrElse result Is DBNull.Value Then
                    Throw New InvalidOperationException("数据库没有返回新记录编号。")
                End If

                Return Convert.ToInt32(result)
            End Using
        End Using
    End Function
End Module

示例中的 ExecuteScalar 用于取得结果集第一行第一列,适合这里通过 OUTPUT INSERTED.ContactId 返回新编号的情况。它不是“执行任意 SQL 并取得所有结果”的方法。若 SQL 返回多行多列,应使用读取器;若只关心受影响行数,通常使用 ExecuteNonQuery。

Using 会在正常结束或发生异常时释放实现了 IDisposable 的对象。代码按“连接包含命令”的方式嵌套,便于看清生命周期。也可以把连接对象交给数据访问层统一管理,但应保证异常时同样得到释放。

五、VB.NET:带条件查询并读取多行结果

查询通常通过 SqlDataReader 顺序读取结果。读取器打开期间,其关联连接应保持可用;读完后要及时释放。下面的函数按姓名查询,并将结果写入控制台:

Imports System.Data
Imports Microsoft.Data.SqlClient

Module ContactQuery
    Public Sub PrintContacts(
        connectionString As String,
        namePrefix As String
    )
        Const sql As String =
            "SELECT ContactId, FullName, Email " &
            "FROM dbo.Contacts " &
            "WHERE FullName LIKE @Prefix " &
            "ORDER BY ContactId;"

        Using connection As New SqlConnection(connectionString)
            Using command As New SqlCommand(sql, connection)
                command.Parameters.Add("@Prefix", SqlDbType.NVarChar, 101).Value =
                    namePrefix & "%"

                connection.Open()

                Using reader As SqlDataReader = command.ExecuteReader()
                    While reader.Read()
                        Dim id As Integer =
                            reader.GetInt32(reader.GetOrdinal("ContactId"))
                        Dim fullName As String =
                            reader.GetString(reader.GetOrdinal("FullName"))

                        Dim emailOrdinal As Integer =
                            reader.GetOrdinal("Email")
                        Dim email As String =
                            If(reader.IsDBNull(emailOrdinal),
                               "(无邮箱)",
                               reader.GetString(emailOrdinal))

                        Console.WriteLine(
                            id & " | " & fullName & " | " & email)
                    End While
                End Using
            End Using
        End Using
    End Sub
End Module

这里的参数长度为 101,是为了容纳最多 100 个字符的输入和附加的通配符。参数长度应按实际列类型与输入规则设计;它不是数据库对所有参数统一适用的固定上限。具体最大长度取决于提供程序、数据库类型和列定义。

LIKE 中的 % 和 _ 是匹配模式字符。参数化可以避免把输入拼成 SQL 语法,但如果用户输入的这些字符不应具有通配含义,还需要按业务规则转义它们,并使用 SQL Server 对应的 ESCAPE 子句。参数化解决的是 SQL 语法与数据混淆问题,不会自动实现所有输入校验或业务语义。

六、参数化查询:作用与边界

参数化查询的主要安全作用,是将用户提供的值作为数据传递,而不是把它拼进 SQL 文本。例如,不要这样写:

Dim sql As String =
    "SELECT ContactId FROM dbo.Contacts WHERE FullName = '" & userInput & "'"

输入中的引号、注释符或其他内容可能改变 SQL 的含义,形成 SQL 注入风险。正确做法是固定 SQL 结构,通过参数传值:

Const sql As String =
    "SELECT ContactId FROM dbo.Contacts WHERE FullName = @FullName"

Using command As New SqlCommand(sql, connection)
    command.Parameters.Add("@FullName", SqlDbType.NVarChar, 100).Value =
        userInput
End Using

需要注意几个边界:

  • 参数用于值,不用于 SQL 结构。不能指望用 @TableName 替代表名,也不能用参数代替列名、关键字或排序方向。若表名、列名或排序方式必须动态选择,应从代码中的允许列表映射到固定 SQL 片段,不能直接使用用户输入。
  • 参数类型和长度应明确。明确使用 SqlDbType 与合适长度,通常比依赖自动推断更容易控制类型转换、字符串比较和查询计划。示例中的字符串参数并不表示它可以安全或正确地装入任意长度。
  • 参数化不等于授权。它不能替代身份认证、行级权限、输入校验、访问控制或最小权限配置。数据库账号仍应只获得业务必需的权限。
  • 参数化不保证业务输入合法。仍需验证必填项、长度、格式、范围和业务规则;错误输入应被明确拒绝或转换,而不是默默截断。

像 AddWithValue 这样的方法可以快速添加参数,但会根据 .NET 值推断参数类型和长度。不同类型的推断可能造成隐式转换、性能退化或与列类型不匹配的问题。对有明确列定义的核心查询,通常应显式指定数据库类型和长度。是否采用其他写法,应结合目标提供程序和测试结果判断。

七、连接字符串与凭据管理

连接字符串包含连接目标和认证配置。SQL Server 中常见的 Windows 集成认证写法如下:

Server=localhost;Database=VbDemo;
Integrated Security=True;
Encrypt=True;
TrustServerCertificate=False;

实际配置应按服务器环境和证书情况调整。加密选项的默认行为可能随驱动版本变化,不要只依赖默认值;应了解所用版本如何处理 TLS 证书,并在生产环境正确配置可信证书。为了“先连上”而关闭证书验证,可能使连接失去对服务器身份的有效验证,不应当作通用修复方法。

如果使用 SQL Server 用户名和密码,连接字符串可采用类似格式:

Server=db.example.internal;Database=VbDemo;
User ID=app_user;Password=从安全配置读取;
Encrypt=True;TrustServerCertificate=False;

不要将真实密码、访问令牌或生产连接串提交到公共代码仓库,也不要把它们写入日志。开发环境可使用环境变量或本机安全配置;部署环境应使用受控的机密管理方案,并限制谁可以读取配置。若凭据曾意外提交,应尽快轮换,而不能只删除文件中的那一行。

连接池会按连接字符串和身份等条件复用连接。不同环境或不同配置可能形成不同的池。通常应采用短生命周期的连接对象,并谨慎对待频繁变化的连接字符串;连接池不是访问控制机制,也不应作为长期保留连接的理由。

八、事务:把相关修改作为一个整体

当多条数据库操作必须全部成功或全部撤销时,应使用事务。以下示例在 SQL Server 中插入一条联系人记录并写入审计记录;审计表需在数据库中另行创建,否则该示例会按预期回滚并报告异常:

Imports System.Data
Imports Microsoft.Data.SqlClient

Public Sub AddContactWithAudit(connectionString As String, fullName As String)
    Const insertContact As String =
        "INSERT INTO dbo.Contacts (FullName, Email) " &
        "VALUES (@FullName, NULL);"

    Const insertAudit As String =
        "INSERT INTO dbo.ContactAudit (ActionName, CreatedAt) " &
        "VALUES (@ActionName, SYSUTCDATETIME());"

    Using connection As New SqlConnection(connectionString)
        connection.Open()

        Using transaction As SqlTransaction = connection.BeginTransaction()
            Try
                Using command As New SqlCommand(
                    insertContact, connection, transaction)
                    command.Parameters.Add(
                        "@FullName", SqlDbType.NVarChar, 100).Value = fullName
                    command.ExecuteNonQuery()
                End Using

                Using command As New SqlCommand(
                    insertAudit, connection, transaction)
                    command.Parameters.Add(
                        "@ActionName", SqlDbType.NVarChar, 50).Value =
                        "ContactCreated"
                    command.ExecuteNonQuery()
                End Using

                transaction.Commit()
            Catch
                Try
                    transaction.Rollback()
                Catch
                    ' 回滚失败时保留原始异常;实际应用还应记录回滚失败。
                End Try
                Throw
            End Try
        End Using
    End Using
End Sub

事务内的命令必须关联同一个连接和事务对象。事务应尽量短,避免在事务中等待用户输入、调用慢速外部服务或执行不必要的工作;长事务可能锁住数据并影响其他请求。不要捕获异常后什么也不做,否则调用方可能误以为操作成功。

九、错误处理与常见故障

数据库操作失败时,应先区分连接问题、认证问题、SQL 错误和数据错误。可以捕获 SqlException 读取错误信息、错误号和适用的诊断字段,同时避免把密码、敏感数据或完整连接串写入日志。

现象常见原因排查方向
无法连接或连接超时服务器地址错误、网络不可达、防火墙阻断、服务未启动、端口配置错误。确认服务器、实例名、端口与网络策略;从应用所在机器测试,而不只在开发者电脑上测试。
登录失败或访问被拒绝认证模式不匹配、账号被禁用、密码错误、缺少数据库权限。核对认证配置和部署身份,按最小权限授予访问权;不要通过提升为管理员来掩盖权限设计问题。
证书或 TLS 错误证书不受信任、主机名不匹配、加密配置与服务器不兼容。检查证书链、服务器名称与驱动版本,正确配置证书;不要默认关闭验证。
找不到表或列数据库选错、架构名遗漏、大小写规则不同、部署脚本未执行。检查连接目标、对象架构和数据库迁移状态,明确使用如 dbo.Contacts 的限定名。
参数类型或数量不匹配参数名拼写错误、忘记传值、类型或长度不符,或命令文本与参数定义不一致。逐项核对 SQL 参数和代码参数;对可空值传入 DBNull.Value,不能用 VB 的 Nothing 代替数据库 NULL。
连接池耗尽或请求变慢连接、读取器未及时释放,连接被长期占用,或数据库本身响应缓慢。使用 Using 释放资源,观察连接池与数据库指标,并检查慢查询;不要仅靠扩大池上限掩盖泄漏。

对可重试的临时网络错误,可以设计有限次数的重试和退避,但不能对所有错误无条件重试。尤其是写入操作:客户端超时不一定表示数据库没有执行。如果操作可能已经提交,再次执行可能产生重复数据。需要重试时应考虑幂等设计、唯一约束或业务操作编号,并依据数据库和驱动的错误分类制定策略。

十、VB6:经典 ADO 的基本写法

维护 VB6 桌面程序时,常见方式是引用 Microsoft ActiveX Data Objects(ADO)库,再用 ADODB.Connection、ADODB.Command 和 ADODB.Recordset。VB6 示例与前文 VB.NET 完全不是同一套 API。旧系统具体可用的 ADO 版本、位数和数据库驱动,取决于机器上安装的组件。

在 VB6 IDE 中,可通过“工程”菜单的“引用”选择已安装的 Microsoft ActiveX Data Objects 库。下面示例使用 SQL Server OLE DB Provider,连接字符串中的服务器、数据库和认证方式需要按环境修改。实际部署还需要目标机器安装匹配的驱动。

Option Explicit

Private Sub AddContact(ByVal fullName As String, ByVal email As String)
    Dim cn As ADODB.Connection
    Dim cmd As ADODB.Command
    Dim rowsAffected As Long
    Dim connectionString As String

    On Error GoTo ErrHandler

    connectionString = _
        "Provider=MSOLEDBSQL;" & _
        "Server=localhost;" & _
        "Database=VbDemo;" & _
        "Trusted_Connection=Yes;" & _
        "Encrypt=Yes;"

    Set cn = New ADODB.Connection
    cn.Open connectionString

    Set cmd = New ADODB.Command
    Set cmd.ActiveConnection = cn
    cmd.CommandType = adCmdText
    cmd.CommandText = _
        "INSERT INTO dbo.Contacts (FullName, Email) " & _
        "VALUES (?, ?)"

    cmd.Parameters.Append cmd.CreateParameter( _
        "pFullName", adVarWChar, adParamInput, 100, fullName)

    If Len(email) = 0 Then
        cmd.Parameters.Append cmd.CreateParameter( _
            "pEmail", adVarWChar, adParamInput, 254, Null)
    Else
        cmd.Parameters.Append cmd.CreateParameter( _
            "pEmail", adVarWChar, adParamInput, 254, email)
    End If

    cmd.Execute rowsAffected, , adExecuteNoRecords

CleanUp:
    On Error Resume Next
    If Not cmd Is Nothing Then Set cmd = Nothing
    If Not cn Is Nothing Then
        If cn.State = adStateOpen Then cn.Close
        Set cn = Nothing
    End If
    Exit Sub

ErrHandler:
    MsgBox "数据库操作失败:" & Err.Description, vbExclamation
    Resume CleanUp
End Sub

此例展示 ADO 参数对象的基本创建方式。参数标记和绑定规则会受到所选 OLE DB Provider 与数据库的影响。对 SQL Server OLE DB 命令,问号占位符常按参数添加顺序对应;不能因为参数对象带有名称,就假定所有 Provider 都会按名称绑定。上线前应使用目标 Provider 验证参数行为、字符类型、空值和 Unicode 数据。

VB6 中传给数据库的 SQL 参数仍然只能表示数据值,不能安全地替代表名或其他 SQL 结构。动态结构应通过允许列表选择固定片段。对于密码、TLS、Provider 版本和连接选项,也应按照实际部署驱动的文档确认,而不是照搬其他机器上的配置。

十一、其他数据库怎么替换

不同数据库在 VB.NET 中通常对应不同的数据提供程序,例如 SQL Server 使用 SqlClient,SQLite 使用 SQLite 提供程序,PostgreSQL 使用相应的 Npgsql 驱动。连接对象和参数类型会随提供程序变化,但“建立连接—创建参数化命令—读取或更新—释放资源”的基本结构相似。

不要把 SQL Server 的连接字符串、数据类型或 SQL 语法原样复制到其他数据库。例如自增列写法、返回新编号的语句、日期函数、分页语法和参数占位符可能不同。切换数据库时,应检查驱动文档及数据库本身的 SQL 方言,并在目标数据库上运行测试。

十二、开发时的检查清单

  • 确认项目是 VB.NET 还是 VB6,引用与代码适用于当前运行时。
  • 确认数据库提供程序已安装或通过项目依赖正确部署,且版本与目标运行环境兼容。
  • 用单独的配置管理连接信息;生产凭据不进入源代码和日志。
  • 用户输入作为参数传递,参数类型、长度和可空规则与数据库定义相符。
  • 动态表名、列名和排序选项只从明确的允许列表中选择。
  • 所有连接、命令和读取器都能在正常与异常路径释放。
  • 需要整体成功或回滚的多步写入使用短事务。
  • 按最小权限配置数据库账号,并在目标网络和部署环境中测试连接。
  • 对超时、证书错误、权限拒绝、重复提交和空值情况做针对性测试。

掌握连接对象的生命周期、参数化查询和错误处理,已经覆盖了大多数 VB 数据库代码的基础。接下来再根据项目规模决定是否引入数据访问层、对象映射工具或异步调用。无论采用哪种封装,参数安全、权限控制、资源释放和数据库语义都不能省略。

© 版权声明

相关文章