
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 数据库程序的基础,但不应被误当作适用于所有数据库和部署规模的通用架构。









