ADO2.20 Class 封装实战:连接字符串、参数化查询与事务避坑指南

发布时间:2026/10/12 17:32:54

ADO2.20 Class 封装实战:连接字符串、参数化查询与事务避坑指南 简介ADO2.20 Class 是一套面向 Visual C 开发者的 ActiveX Data Objects 类库由非中文开发者在 CodeProject 上发布旨在简化 VC 程序对 SQL Server、Oracle、Access 等数据源的访问让开发者无需深入底层 ADO 接口即可完成数据库操作。资源包共 3 个文件包含 1 个 cpp 实现文件、1 个 h 头文件以及 1 个 mht 说明文档压缩包约 105KB体积轻巧便于集成到现有工程。其中头文件提供类定义与函数声明cpp 文件封装了 Connection、Command、Recordset 等核心对象的常用操作mht 文档则保留了原版使用说明与示例方便对照查阅。目前已有 126 人学习下载适合不熟悉 ADO 或需要快速为 VC 项目添加数据库访问能力的开发者参考可借此理解连接字符串配置、SQL 执行与结果集遍历的封装思路并直接复用源码缩短开发周期。1. ADO2.20 Class从连接字符串到记录集一条被忽视的稳定链路很多人第一次接触 ADO2.20 Class是在维护一套老系统的数据访问层。代码里到处是ADODB.Connection、ADODB.Recordset没有 ORM没有连接池封装甚至连参数化查询都是手写的。你可能会觉得这套东西早该淘汰了但现实是大量工业控制、医疗设备、财务对账类软件至今仍跑在 ADO 2.20 之上原因很简单——它足够薄薄到你能完全掌控每一次数据库交互的字节流向。ADO2.20 Class 并不是某个具体类库的名字而是围绕 ADO 2.20 版本组件封装出来的一套数据访问类。它解决的核心问题是把 Connection、Command、Recordset 三个对象的生命周期管理、错误处理和类型转换收敛到一个可复用的类里避免在每个业务函数里重复写Open、Close、Set conn Nothing。适合谁适合那些需要维护或重构 VB6、VBScript、早期 ASP、Delphi 遗留系统又不想引入重型框架的工程师。接下来的内容我会按“先跑通最小链路再拆参数再讲避坑最后给一个验证技巧”的顺序展开每一步都能直接抄。2. 把 ADO2.20 Class 跑起来最小可用封装与连接字符串的四种写法2.1 为什么不用原生 ADODB 而要多封一层原生 ADODB 用起来不难难在一致性。一个项目里如果有二十个地方打开连接就有二十种错误处理风格有的用On Error Resume Next吞掉异常有的在Err.Number判断后直接Resume还有的干脆不关连接。ADO2.20 Class 的价值不在于性能提升而在于把“打开-执行-关闭”这个固定动作变成不可绕过的模板。我一般会封一个CDataAccess类暴露三个方法Execute用于无返回集的增删改Query返回一个二维数组或 RecordsetBeginTrans/CommitTrans/RollbackTrans用于事务。内部持有ADODB.Connection和可选的ADODB.Command。这样做的好处是当你要从 SQL Server 换到 Access 或 Oracle 时只需要改连接字符串和少量方言业务层代码不动。2.2 最小可用类的代码骨架下面是一个 VB6 风格的类模块骨架去掉了具体业务逻辑只保留 ADO2.20 的核心调用。如果你用 VBScript 或 VBA把Private换成Dim把Class_Initialize换成普通初始化函数即可。 CDataAccess.cls Private m_conn As ADODB.Connection Private m_cmd As ADODB.Command Private Sub Class_Initialize() Set m_conn New ADODB.Connection Set m_cmd New ADODB.Command m_conn.CursorLocation adUseClient 客户端游标便于断开后继续用 Recordset m_conn.CommandTimeout 30 秒防止长查询卡死界面 End Sub Public Sub OpenConnection(ByVal connStr As String) On Error GoTo ErrHandler If m_conn.State adStateOpen Then m_conn.Close m_conn.Open connStr Exit Sub ErrHandler: Err.Raise Err.Number, CDataAccess.OpenConnection, Err.Description End Sub Public Function Query(ByVal sql As String) As ADODB.Recordset Dim rs As ADODB.Recordset Set rs New ADODB.Recordset rs.CursorLocation adUseClient rs.Open sql, m_conn, adOpenStatic, adLockReadOnly Set Query rs End Function Public Sub Execute(ByVal sql As String) m_conn.Execute sql, , adExecuteNoRecords End Sub Private Sub Class_Terminate() If Not m_conn Is Nothing Then If m_conn.State adStateOpen Then m_conn.Close Set m_conn Nothing End If End Sub逻辑说明CursorLocation设为adUseClient是关键一步。ADO 2.20 默认使用服务器端游标Recordset 一旦断开连接就不可用改成客户端游标后Recordset 会一次性把数据拉到本地断开后仍可遍历。代价是内存占用上升所以只适合结果集不大的场景。CommandTimeout默认是 30 秒但很多老系统在慢查询时直接卡死显式设成 30 或 60 能避免无限等待。参数说明adOpenStatic表示静态游标适合只读遍历adLockReadOnly表示不锁定记录减少数据库压力。如果你需要更新改成adLockOptimistic但要注意并发冲突。2.3 连接字符串的四种写法与选择依据连接字符串是 ADO2.20 Class 最容易翻车的地方。同一个数据库不同写法在超时、编码、权限上表现完全不同。下面这张表是我在维护老系统时总结的对比以 SQL Server 为例Access 和 Oracle 的差异在备注里说明。写法示例片段适用场景注意点OLE DB ProviderProviderSQLOLEDB;Data Source...传统 ADO 2.20 默认需要装 MDACWin10 以上可能缺失ODBC DriverDriver{SQL Server};Server...兼容性最好性能略低但几乎不会因组件缺失失败DSN 文件FileDSNxxx.dsn多环境切换部署时要带 dsn 文件路径不能写死无 DSN 的 ODBCDriver{SQL Server Native Client 11.0};...新驱动驱动版本要和目标机器一致Access 数据库用ProviderMicrosoft.Jet.OLEDB.4.0或Microsoft.ACE.OLEDB.12.0后者支持 64 位但需要单独安装。Oracle 用ProviderOraOLEDB.Oracle注意Data Source写的是 TNS 别名而不是 IP。提示连接字符串里的User ID和Password不要硬编码在类里用配置文件或环境变量传入。老系统常见做法是写在一个.ini里类初始化时读取。2.4 用 Command 对象做参数化查询直接拼接 SQL 是 ADO2.20 Class 最大的安全隐患。正确做法是用ADODB.Command加Parameters。下面是一个按 ID 查询的示例Public Function QueryById(ByVal id As Long) As ADODB.Recordset Dim rs As ADODB.Recordset Set rs New ADODB.Recordset With m_cmd .ActiveConnection m_conn .CommandText SELECT * FROM Orders WHERE OrderID ? .CommandType adCmdText .Parameters.Refresh 让 Provider 推断参数类型 .Parameters(0).Value id End With rs.CursorLocation adUseClient rs.Open m_cmd, , adOpenStatic, adLockReadOnly Set QueryById rs End Function逻辑说明Parameters.Refresh会向数据库请求参数元数据对于 SQL Server 和 Oracle 都有效但对某些 Access 驱动可能返回空。如果 Refresh 失败就手动CreateParameter并指定类型。?是 OLE DB 的占位符不是所有驱动都支持命名参数所以统一用问号最稳。参数说明adCmdText表示命令文本是 SQL 语句如果调用存储过程改成adCmdStoredProc参数用p1或直接按顺序传。注意Parameters(0)的索引从 0 开始不是 1。3. ADO2.20 Class 的四个必调参数与事务处理3.1 CursorLocation 选客户端还是服务器端这是 ADO2.20 里最容易被忽略、又最影响稳定性的参数。服务器端游标adUseServer在连接断开后 Recordset 立即失效而且对数据库的锁持有时间更长。客户端游标adUseClient把数据缓存在本地断开后仍可读适合“取数据-关连接-慢慢处理”的模式。但客户端游标不是银弹。如果结果集超过几万行内存会飙升而且RecordCount虽然准确但代价是全部拉取。我的经验是报表类查询用客户端游标逐行处理的大批量更新用服务器端游标加只进游标adOpenForwardOnly。3.2 LockType 与并发冲突的取舍adLockReadOnly最安全不锁记录。adLockOptimistic只在更新瞬间锁记录适合并发不高的场景。adLockBatchOptimistic配合UpdateBatch可以批量提交减少网络往返但冲突时要自己处理Status字段。老系统里常见的一个坑是用adLockOptimistic打开 Recordset然后遍历修改最后UpdateBatch。如果中途有人改了同一行UpdateBatch会报错但错误信息很模糊。解决方法是遍历后检查rs.Status对adRecConcurrencyViolation的记录重新读取再合并。3.3 CommandTimeout 与 ConnectionTimeout 的区别ConnectionTimeout控制建立连接的超时默认 15 秒。CommandTimeout控制命令执行超时默认 30 秒。很多开发者只设了前者结果查询慢的时候界面卡死几十秒。正确做法是两个都显式设置并且CommandTimeout根据业务设成 60 或 120。注意CommandTimeout设为 0 表示无限等待生产环境不要这么干否则一个慢查询能把线程池占满。3.4 事务处理的正确姿势ADO2.20 的事务用Connection.BeginTrans、CommitTrans、RollbackTrans。关键点是事务必须绑定在同一个 Connection 上不能跨连接。下面是一个转账的伪代码示例Public Sub Transfer(ByVal fromId As Long, ByVal toId As Long, ByVal amount As Currency) On Error GoTo ErrHandler m_conn.BeginTrans m_conn.Execute UPDATE Accounts SET Balance Balance - amount WHERE AccountID fromId m_conn.Execute UPDATE Accounts SET Balance Balance amount WHERE AccountID toId m_conn.CommitTrans Exit Sub ErrHandler: m_conn.RollbackTrans Err.Raise Err.Number, CDataAccess.Transfer, Err.Description End Sub逻辑说明这里为了演示简化了 SQL 拼接实际应该用 Command 参数化。BeginTrans之后的所有Execute都在同一事务里任何一步出错就RollbackTrans。注意RollbackTrans本身也可能出错所以最好再包一层On Error Resume Next。参数说明BeginTrans返回一个隔离级别默认是adXactReadCommitted。如果需要更高级别传adXactSerializable但会降低并发。4. ADO2.20 Class 避坑记录五个让老系统半夜报警的细节4.1 现象Recordset 关闭后仍占用内存进程越来越大原因客户端游标把数据缓存在本地Recordset.Close只是释放了游标但底层Fields集合和Properties集合可能还持有引用。如果循环里反复创建 Recordset 而不显式Set rs Nothing内存不会及时回收。解决每次用完 Recordset 后先rs.Close再Set rs Nothing。在类模块里把 Recordset 作为局部变量而不是成员变量让它在函数退出时自动释放。4.2 现象连接字符串在开发机正常部署到客户机报“未找到提供程序”原因开发机装了 MDAC 或 SQL Native Client客户机只有系统自带的 ODBC 驱动。ProviderSQLOLEDB在 Win10 以上可能不存在。解决改用 ODBC 写法Driver{SQL Server};...或者把驱动安装包一起部署。更稳妥的做法是在类初始化时用On Error尝试多种连接字符串取第一个成功的。4.3 现象参数化查询报“参数类型不匹配”原因Parameters.Refresh推断的类型和实际传入的值不一致。比如数据库字段是Decimal你传了Double某些驱动会拒绝。解决手动CreateParameter并指定adDecimal或adNumeric设置Precision和Scale。或者统一用adVarWChar传字符串让数据库隐式转换但这样会失去索引优势。4.4 现象事务提交后数据没生效或者部分生效原因Connection在执行BeginTrans之前已经隐式开启了一个事务或者Execute语句里包含了COMMIT。另外如果用了连接池CommitTrans可能被池化层拦截。解决确保BeginTrans之前没有未提交的操作。不要在 SQL 里写BEGIN TRAN。如果用了 MTS 或 COM事务要交给容器管理不要手动BeginTrans。4.5 现象多线程环境下 ADO2.20 Class 随机崩溃原因ADO 的 Connection 和 Recordset 不是线程安全的。VB6 的单元线程模型里每个线程要自己创建 Connection不能共享。解决每个线程独立创建CDataAccess实例或者用对象池加锁。如果必须共享用ADODB.Connection的Marshal接口但复杂度很高不如改成每个线程一个连接。5. 用 ADO2.20 Class 做一次可复现的验证从建表到断连遍历5.1 验证目标与准备这一章给一个完整的验证流程用来确认你的 ADO2.20 Class 封装是否真的能在断开连接后继续使用 Recordset。准备一个空的 Access 数据库或 SQL Server 库建一张TestItems表字段ID自增Name文本。CREATE TABLE TestItems ( ID INT IDENTITY(1,1) PRIMARY KEY, Name NVARCHAR(50) ); INSERT INTO TestItems (Name) VALUES (Alpha), (Beta), (Gamma);5.2 断开连接后遍历 Recordset 的代码Public Sub TestDisconnectedRecordset() Dim da As New CDataAccess Dim rs As ADODB.Recordset da.OpenConnection ProviderSQLOLEDB;Data Source.;Initial CatalogTestDB;Integrated SecuritySSPI; Set rs da.Query(SELECT * FROM TestItems ORDER BY ID) 关键关闭连接模拟断开 da.CloseConnection Do While Not rs.EOF Debug.Print rs.Fields(ID).Value, rs.Fields(Name).Value rs.MoveNext Loop rs.Close Set rs Nothing Set da Nothing End Sub逻辑说明Query内部用了adUseClient所以CloseConnection之后 Recordset 仍然可用。如果这里报“连接已关闭”说明 CursorLocation 没设对。Debug.Print在 VB6 里输出到立即窗口VBA 里同样可用。参数说明Integrated SecuritySSPI表示用 Windows 身份验证不需要用户名密码。如果要用 SQL 验证改成User IDsa;Passwordxxx。5.3 验证事务回滚是否彻底再写一个测试插入一条记录然后故意让第二条插入失败检查第一条是否回滚。Public Sub TestRollback() Dim da As New CDataAccess da.OpenConnection ProviderSQLOLEDB;Data Source.;Initial CatalogTestDB;Integrated SecuritySSPI; On Error GoTo ErrHandler da.BeginTrans da.Execute INSERT INTO TestItems (Name) VALUES (Delta) da.Execute INSERT INTO TestItems (ID, Name) VALUES (1, Epsilon) 主键冲突 da.CommitTrans Exit Sub ErrHandler: da.RollbackTrans Debug.Print Rolled back: Err.Description End Sub运行后查TestItems表应该只有 Alpha、Beta、Gamma 三条没有 Delta。如果 Delta 还在说明RollbackTrans没生效检查是否在BeginTrans之前有未提交的操作。5.4 一个容易被忽略的收尾习惯每次验证完把CDataAccess实例置空并且确认数据库里的连接数没有残留。在 SQL Server 里可以查sys.dm_exec_sessions看有没有状态为sleeping的会话。如果有说明Class_Terminate里的Close没执行可能是对象被循环引用卡住了。我自己的习惯是在类里加一个Dispose方法显式关闭连接和清空对象然后在业务代码的Finally块里调用。不要依赖Class_Terminate因为 VB6 的引用计数在循环引用时会失效。这个习惯帮我省掉了无数次半夜被告警叫醒的麻烦。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/12 17:32:54

量能跌破1.5万亿:牛没死,死的是惯性

《量能跌破1.5万亿:牛没死,死的是惯性》——修复看业绩,反转看量能;这一轮周期里,耐心是新的仓位六月,A股一天成交三万亿,热闹得像除夕。九月最后两个交易日,不到一万五千亿&#xf…

2026/10/12 17:32:53

前端工具集ztools:按需引入、tree-shaking与核心函数实现

简介:ztools 是一套面向 JavaScript 前端开发者的轻量级工具集,适合希望提升异步编程、视图渲染与代码解耦能力的初中级开发者。它围绕三个核心模块展开:兼容旧版 IE 的 ES6 Promise 实现,帮助在低版本浏览器中编写现代异步代码&a…

2026/10/12 17:27:51

深入理解Python的for与with:从迭代机制到资源管理的进阶实践

在 Python 里待得久了,你会发现真正决定代码水平和写代码幸福感的东西,往往不是那些花哨的框架和库,而是对语言本身这些基础语法的理解深度。for和with,这两个关键字几乎天天见,但很多人对它们的认知停留在“循环用 fo…

2026/10/12 19:58:33

基于COMSOL的输变电工程工频电磁场仿真与环评应用实践

最近在做某输变电工程的环评支撑时,正好碰到一段三相高压电缆要从居住区地下穿过。评审专家和住户最关心的就一件事:电缆埋在地下,地面的人站久了到底有没有影响?这个问题落到技术层面,其实是两个量:电缆运…

2026/10/12 19:58:33

大模型能力爆发:界面生成、合规监管与端侧部署的实战解析

10月的AI圈像是被按了快进键。我早上还在刷一组产品更新,下午就看到三条消息前后脚占据了讨论区:新一代(第六代)旗舰大模型全面开放,还突然“长出了界面”;某头部AI公司在一个海外市场公开道歉,…

2026/10/12 19:58:33

Qt触摸屏滑动卡死:QScroller与事件冲突的排查与优化

1. 触摸屏滑动卡死:一个让Qt桌面端开发者头疼的经典问题如果你做过带触摸屏的Qt桌面项目,尤其是那种跑在工控机、自助终端、医疗设备或者车载中控上的应用,大概率遇到过这个场景:手指在QTableView、QTableWidget或者QListWidget上…

2026/10/12 19:58:33

open-code-review:一种提升代码可理解性与可维护性的评审方法论

1. “open-code-review”不是开源项目,而是一套可落地的代码评审实践方法论“open-code-review”这个词乍看像某个 GitHub 上的新仓库名,或是某款刚发布的开源工具——但实际翻遍主流代码托管平台、技术社区和工具索引库,都找不到一个以该名称…

2026/10/12 19:58:33

YOLO抽烟目标检测数据集:从标注校验到训练避坑全指南

简介:面向计算机视觉目标检测项目与YOLO系列算法学习者,这份数据集聚焦公共场所吸烟行为识别,提供训练模型所需的原始图像与标定数据。压缩包内共594个文件,由297张jpg图片和297个xml标注文件一一对应组成;xml中记录目…

2026/10/11 0:02:13

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/12 12:01:26

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/12 12:54:07

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/12 0:04:22

绝缘子缺陷检测数据集清洗与工业级训练实战指南

简介:本资源是面向电力AI研发人员、工业视觉工程师及智能巡检系统开发者的绝缘子缺陷检测专用YOLO格式数据集,解决无人机航拍场景下绝缘子破损、污闪、积雪等9类典型缺陷的精准识别与定位难题。数据集共2139张真实巡检图像(含训练/验证/测试集…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑