VB数据库编程实例:用 ADO 实现增删改查

科技资讯18小时前发布 jdkjadmin
0 00
VB数据库编程实例:用 ADO 实现增删改查

VB数据库编程实例:用 ADO 实现增删改查

Visual Basic 6.0(下文简称 VB6)虽然是较早期的开发环境,但在维护旧系统、开发内部工具或处理已有 Access 数据库时仍可能用到。ADO(ActiveX Data Objects)提供了一套访问数据源的对象接口,VB6 程序可以通过它连接数据库、执行 SQL,并读取查询结果。

本文以 VB6 和 Access 数据库为例,按从准备数据库到编写增删改查代码的顺序,给出一个可以放进标准模块的完整示例。示例使用参数化命令,涵盖连接、记录集读取、错误处理和资源释放,也会说明它的适用范围与边界。

一、ADO 适合什么场景

ADO 是访问数据源的组件接口,本身并不是数据库。程序仍需通过相应的 OLE DB 提供程序连接 Access、SQL Server 等数据源。ADO 常用于已有 VB6 桌面程序,或需要通过经典 ADO 接口访问数据库的维护项目。

本文选择 Access,是因为它便于展示,不需要另行部署数据库服务器。这个组合适合单机工具、小规模内部程序或维护已有 Access 数据库的场景。它不意味着 Access 适合所有应用:如果程序需要多用户并发写入、集中管理权限、远程访问或更强的事务与运维能力,应根据实际需求评估数据库服务器,而不是只把示例改成一个连接字符串就投入生产。

ADO 可以连接不同数据源,但连接字符串、SQL 方言、数据类型、参数规则和驱动能力会随提供程序变化。以下代码采用 Access 的 Jet OLE DB 提供程序,不是适用于所有数据库的通用写法。

二、准备 VB6 和 Access 环境

1. 添加 ADO 引用

在 VB6 IDE 中打开“工程”菜单下的“引用”,勾选一项可用的 Microsoft ActiveX Data Objects 类型库,例如 Microsoft ActiveX Data Objects 2.8 Library。版本名称会随系统安装情况有所不同。本文使用早期绑定,因此代码里可以直接声明 ADODB.Connection、ADODB.Command 和 ADODB.Recordset 等类型。

2. 创建示例数据库和表

创建一个 Access 数据库文件 demo.mdb,在其中建立 Customers 表。字段定义如下:

  • CustomerID:自动编号,设为主键。
  • CustomerName:短文本,长度 100。
  • Email:短文本,长度 200,允许为空。

也可以在 Access 的查询设计中切换到 SQL 视图,执行下面的建表语句。不同 Access 版本对 DDL 语法的支持可能有差异;若建表语句报错,直接通过表设计界面建立相同字段即可。

CREATE TABLE Customers (
    CustomerID AUTOINCREMENT PRIMARY KEY,
    CustomerName TEXT(100) NOT NULL,
    Email TEXT(200)
);

将数据库文件放在 VB6 工程生成目录下,或者在代码中填写它的绝对路径。示例使用绝对路径,以避免程序工作目录变化导致连接到错误位置。

3. 选择匹配的 OLE DB 提供程序

Jet 4.0 常用于连接传统的 .mdb 文件。Jet 4.0 是 32 位提供程序,因此通常需要 32 位 VB6 程序,并且运行程序的 Windows 环境能够提供相应组件。较新的系统也可能使用 ACE OLE DB 提供程序连接 Access 文件,例如 .accdb;ACE 的版本、安装情况和 32/64 位必须与进程匹配。若出现“找不到可安装的 ISAM”“未注册提供程序”一类错误,先检查提供程序是否安装、连接字符串是否写对以及进程与提供程序的位数是否一致。

三、ADO 的几个核心对象

  • ADODB.Connection:表示数据库连接,负责打开和关闭数据源,也可执行不需要返回记录集的命令。
  • ADODB.Command:表示一条待执行命令,可指定 SQL、连接和参数,适合增删改与参数化查询。
  • ADODB.Recordset:表示查询得到的记录集,可逐行读取字段。

ADO 调用成功不代表业务操作一定符合预期。比如更新语句执行成功,但影响行数为零,可能是目标 ID 不存在。示例会检查受影响行数,并将它作为调用者可判断的结果。

四、完整 VB6 模块示例

新建一个标准模块,例如 modCustomerADO.bas,确认已添加 ADO 引用,再把数据库路径改成实际文件位置。以下模块可由窗体按钮调用,也包含一个用于演示的 DemoCRUD 过程。它会新增一条记录、查询记录、修改记录、再次查询并删除记录,因此运行后会实际改变数据库内容。

Option Explicit

Private Const DB_FILE As String = "C:\VB6Demo\demo.mdb"

' 打开连接。调用方负责在使用完毕后关闭连接。
Private Function OpenDatabase() As ADODB.Connection
    Dim cn As ADODB.Connection
    Set cn = New ADODB.Connection

    cn.ConnectionString = _
        "Provider=Microsoft.Jet.OLEDB.4.0;" & _
        "Data Source=" & DB_FILE & ";" & _
        "Persist Security Info=False;"
    cn.Open

    Set OpenDatabase = cn
End Function

' 新增一条客户记录,成功时返回数据库生成的主键。
Public Function AddCustomer(ByVal customerName As String, _
                            ByVal email As String) As Long
    Dim cn As ADODB.Connection
    Dim cmd As ADODB.Command
    Dim rs As ADODB.Recordset
    Dim newID As Long

    On Error GoTo ErrorHandler

    Set cn = OpenDatabase()

    Set cmd = New ADODB.Command
    Set cmd.ActiveConnection = cn
    cmd.CommandType = adCmdText
    cmd.CommandText = _
        "INSERT INTO Customers (CustomerName, Email) VALUES (?, ?)"

    ' Jet/Access 的 ? 参数按位置绑定,添加顺序必须与 SQL 中的顺序一致。
    cmd.Parameters.Append cmd.CreateParameter( _
        "pName", adVarWChar, adParamInput, 100, customerName)
    cmd.Parameters.Append cmd.CreateParameter( _
        "pEmail", adVarWChar, adParamInput, 200, email)

    cmd.Execute

    ' @@IDENTITY 在当前连接中读取最近生成的自动编号。
    ' 必须使用执行 INSERT 的同一个连接。
    Set rs = cn.Execute("SELECT @@IDENTITY AS NewID")
    If Not rs.EOF Then newID = CLng(rs.Fields("NewID").Value)

    AddCustomer = newID

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

ErrorHandler:
    Dim errorMessage As String
    errorMessage = Err.Description
    AddCustomer = 0
    MsgBox "新增客户失败:" & errorMessage, vbExclamation
    Resume CleanUp
End Function

' 按主键更新客户资料。返回受影响的记录数。
Public Function UpdateCustomer(ByVal customerID As Long, _
                               ByVal customerName As String, _
                               ByVal email As String) As Long
    Dim cn As ADODB.Connection
    Dim cmd As ADODB.Command
    Dim affected As Long

    On Error GoTo ErrorHandler

    Set cn = OpenDatabase()

    Set cmd = New ADODB.Command
    Set cmd.ActiveConnection = cn
    cmd.CommandType = adCmdText
    cmd.CommandText = _
        "UPDATE Customers SET CustomerName = ?, Email = ? " & _
        "WHERE CustomerID = ?"

    cmd.Parameters.Append cmd.CreateParameter( _
        "pName", adVarWChar, adParamInput, 100, customerName)
    cmd.Parameters.Append cmd.CreateParameter( _
        "pEmail", adVarWChar, adParamInput, 200, email)
    cmd.Parameters.Append cmd.CreateParameter( _
        "pID", adInteger, adParamInput, , customerID)

    cmd.Execute affected, , adExecuteNoRecords
    UpdateCustomer = affected

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

ErrorHandler:
    Dim errorMessage As String
    errorMessage = Err.Description
    UpdateCustomer = -1
    MsgBox "更新客户失败:" & errorMessage, vbExclamation
    Resume CleanUp
End Function

' 按主键删除客户。返回受影响的记录数。
Public Function DeleteCustomer(ByVal customerID As Long) As Long
    Dim cn As ADODB.Connection
    Dim cmd As ADODB.Command
    Dim affected As Long

    On Error GoTo ErrorHandler

    Set cn = OpenDatabase()

    Set cmd = New ADODB.Command
    Set cmd.ActiveConnection = cn
    cmd.CommandType = adCmdText
    cmd.CommandText = _
        "DELETE FROM Customers WHERE CustomerID = ?"

    cmd.Parameters.Append cmd.CreateParameter( _
        "pID", adInteger, adParamInput, , customerID)

    cmd.Execute affected, , adExecuteNoRecords
    DeleteCustomer = affected

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

ErrorHandler:
    Dim errorMessage As String
    errorMessage = Err.Description
    DeleteCustomer = -1
    MsgBox "删除客户失败:" & errorMessage, vbExclamation
    Resume CleanUp
End Function

' 读取并显示全部客户。实际程序中可改为填充 ListView、网格或其他控件。
Public Sub ListCustomers()
    Dim cn As ADODB.Connection
    Dim cmd As ADODB.Command
    Dim rs As ADODB.Recordset
    Dim output As String

    On Error GoTo ErrorHandler

    Set cn = OpenDatabase()

    Set cmd = New ADODB.Command
    Set cmd.ActiveConnection = cn
    cmd.CommandType = adCmdText
    cmd.CommandText = _
        "SELECT CustomerID, CustomerName, Email " & _
        "FROM Customers ORDER BY CustomerID"

    Set rs = cmd.Execute

    Do Until rs.EOF
        output = output & CStr(rs.Fields("CustomerID").Value) & " | " & _
            CStr(rs.Fields("CustomerName").Value) & " | " & _
            SafeText(rs.Fields("Email").Value) & vbCrLf
        rs.MoveNext
    Loop

    If Len(output) = 0 Then output = "没有客户记录。"
    MsgBox output, vbInformation, "客户列表"

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

ErrorHandler:
    MsgBox "读取客户失败:" & Err.Description, vbExclamation
    Resume CleanUp
End Sub

' 数据库 NULL 不能直接当作普通字符串使用。
Private Function SafeText(ByVal value As Variant) As String
    If IsNull(value) Then
        SafeText = ""
    Else
        SafeText = CStr(value)
    End If
End Function

' 仅供演示:执行新增、查询、更新、查询和删除。
Public Sub DemoCRUD()
    Dim customerID As Long
    Dim affected As Long

    customerID = AddCustomer("示例客户", "[email protected]")
    If customerID = 0 Then Exit Sub

    MsgBox "新增成功,CustomerID=" & CStr(customerID), vbInformation
    ListCustomers

    affected = UpdateCustomer(customerID, "修改后的客户", _
                              "[email protected]")
    If affected < 0 Then Exit Sub
    MsgBox "更新影响记录数:" & CStr(affected), vbInformation
    ListCustomers

    affected = DeleteCustomer(customerID)
    If affected < 0 Then Exit Sub
    MsgBox "删除影响记录数:" & CStr(affected), vbInformation
End Sub

若从窗体按钮调用,可在按钮的 Click 事件中写:

Private Sub cmdDemo_Click()
    DemoCRUD
End Sub

五、逐项理解增删改查

新增:INSERT 与自动编号

INSERT INTO ... VALUES (?, ?) 中的两个问号对应客户名称和邮箱。示例先执行 INSERT,再在同一个连接上执行 SELECT @@IDENTITY 获取自动编号。连接必须相同;如果关闭连接后再查询,便不能可靠地得到刚插入记录的编号。

新增函数以 Long 返回新 ID,约定返回 0 表示失败。生产代码也可以改用明确的结果对象或错误返回机制,避免把某个合法值和错误状态混在一起。无论采用哪种方式,都应记录详细错误信息,并让界面给用户提供合适提示。

查询:Recordset 与 NULL

cmd.Execute 返回记录集,程序通过 EOF 判断是否到达末尾,再用 MoveNext 移动到下一行。记录集关闭后,应释放对象引用。示例中的 SafeText 处理邮箱字段为 NULL 的情况:数据库的 NULL 表示未知或缺失,并不等同于空字符串,不能总是直接用 CStr 转换。

真实界面不宜无条件把大量记录拼成一个 MsgBox 字符串。更常见的做法是将记录显示在网格或列表控件中,并对结果数量、分页和排序作出明确处理。

更新:WHERE 条件不能省略

UPDATE Customers ... WHERE CustomerID = ? 通过主键限定目标记录。若漏掉 WHERE,更新语句会作用于表中所有行。删除语句也有同样风险。编写和调试修改类 SQL 时,应先确认筛选条件,再在测试数据上运行。

删除:检查影响行数

删除成功执行不一定代表确实找到目标记录。affected 通常用于判断受影响记录数:0 表示没有记录符合条件,1 表示影响一条。在 Access OLE DB 提供程序和具体命令条件下,返回值行为可能有细节差异,应用应结合测试验证,不要把执行未报错简单等同于业务对象一定存在。

六、参数和安全边界

参数化查询的主要作用,是把 SQL 结构与数据值分开,让数据库提供程序按参数类型处理输入,避免把用户输入直接拼接进 SQL。示例中的姓名、邮箱和 ID 都通过 CreateParameter 传入,而不是这样拼接:

sql = "SELECT * FROM Customers WHERE CustomerName = '" & userInput & "'"

直接拼接不但容易在引号、特殊字符等情况下出错,也可能造成 SQL 注入。参数化查询可以显著降低这类风险,但它不是完整的应用安全方案:仍需进行业务校验、权限控制、错误信息管理和数据保护。示例连接串中的 Persist Security Info=False 也不等于数据库文件已获得访问控制保护。Access 文件的实际安全性还取决于文件权限、部署位置、备份和使用方式。

参数不能用来替代表名、字段名、SQL 关键字或排序方向。例如,不能把 ORDER BY ? 当作可靠的字段名参数。动态标识符必须通过白名单选择,再拼入 SQL;外部输入不能未经校验就成为 SQL 结构。

Access 的 Jet OLE DB 提供程序中,命令文本的参数通常以问号占位,并按出现顺序绑定。参数对象的名称,如 pName,主要用于代码可读性;不要假定 Jet 会像某些其他数据库驱动那样按名称匹配参数。因此,SQL 中占位符的先后顺序必须与 Parameters.Append 的顺序一致。

参数类型和长度要与字段定义相符。示例对短文本使用 adVarWChar,对自动编号主键使用 adInteger。若字段类型、提供程序或数据库版本不同,可能需要采用不同的 ADO 类型;遇到类型不匹配错误时,应核对数据库字段属性、参数类型和长度,而不是盲目删除参数类型设置。

可空字段需要明确处理。示例把邮箱作为普通文本传入,空字符串表示存入空字符串,不会自动变成数据库 NULL。如果业务要求“没有邮箱”存为 NULL,应根据界面输入将参数值设为 VB 的 Null,并确认目标字段允许空值。字段为 NOT NULL 时,传入 Null 会导致数据库错误。

七、常见错误与排查方法

1. 找不到提供程序或数据库文件

先核对 DB_FILE 是否指向实际存在的文件,并确认运行程序的用户对目录有读写权限。随后检查 Jet 或 ACE 是否安装、位数是否与应用匹配、连接字符串里的 Provider 名称是否对应实际安装的提供程序。连接字符串中路径包含特殊字符时,也应留意引号和路径拼写。

2. 参数数量、顺序或类型错误

逐个对照 SQL 中问号数量与 Parameters.Append 调用数量,再核对添加顺序。文本参数的长度不能小于字段允许的有效输入长度;数值字段不要误用文本类型。不同提供程序对参数类型的支持不完全相同,换了数据库或驱动后应重新验证。

3. 查询结果中出现 NULL 转换错误

读取可能为空的字段时,先使用 IsNull 判断。不要假设数据库中的可空字段一定有值,也不要把 NULL 与空字符串混为一谈。

4. 更新或删除影响了多条记录

检查 WHERE 条件是否正确,以及参数是否按正确位置传入。对关键数据执行删除前,先在测试库验证 SQL,必要时让用户确认目标记录。只依赖界面显示的 ID 并不够,数据库端的条件仍须可靠。

5. 数据库文件被锁定或并发写入失败

Access 文件需要放在所有相关用户可访问且适合共享的位置。网络断开、权限不足、多个用户同时修改同一文件或文件异常,都可能导致锁定和写入问题。ADO 不会消除文件级数据库本身的并发和部署限制。对持续多人同时写入的业务,应评估服务端数据库及相应的账号、备份和运维方案。

6. 错误处理隐藏了真正问题

示例在错误处理分支保存 Err.Description 后清理资源,并在正常路径也关闭连接。这种结构的重点是无论成功失败都尽量释放 Recordset 和 Connection。实际项目中不应长期依靠 On Error Resume Next 吞掉错误;示例只在清理阶段短暂使用它,随后退出过程。应将连接错误、参数错误等信息记录到日志,并避免将敏感细节直接暴露给最终用户。

八、事务和进一步改进

当一项业务由多条写入语句组成,且要求全部成功或全部撤销时,应考虑事务。ADO Connection 提供 BeginTrans、CommitTrans 和 RollbackTrans。但事务支持和锁定行为取决于数据源与提供程序,不能仅凭代码中调用了事务方法,就假定所有数据库都提供完全相同的并发语义。使用 Access 时尤其应在目标提供程序和实际部署环境中验证。

本文的增删改函数每次各自打开连接,结构直观,适合小型示例。若应用频繁访问数据库,可根据生命周期设计连接管理,但必须避免把一个全局连接长期暴露给不相关的界面代码,也要处理连接失效、并发调用和异常关闭。不要仅为减少几行代码就引入无法维护的共享状态。

此外,示例使用固定数据库路径、简单的消息框反馈和单表操作,目的是突出 ADO 的基本调用方式。投入实际使用时,还应根据程序结构加入输入校验、日志、统一错误处理、界面状态反馈、数据库备份与升级策略,并确认数据库文件权限符合部署要求。

总结

用 VB6 和 ADO 实现 Access 增删改查,核心步骤是:配置与进程匹配的 OLE DB 提供程序,创建并打开 ADODB.Connection,使用 ADODB.Command 执行参数化 SQL,用 ADODB.Recordset 读取查询结果,并在所有路径上关闭和释放对象。

需要特别记住的是:Access/Jet 的问号参数按位置绑定;参数用于数据值,不用于表名或字段名;NULL 必须单独处理;更新和删除要有明确条件;提供程序、位数和数据库字段类型必须匹配。掌握这些边界后,这套示例可以作为维护 VB6 数据库程序的基础,但不应被误当作适用于所有数据库和部署规模的通用架构。

© 版权声明

相关文章