ADO 2.20 类库详解:Connection/Command/Recordset 与旧系统维护实战

ADO 2.20 类库详解:Connection/Command/Recordset 与旧系统维护实战 简介这套ADO2.20 Class是面向Visual C开发者的数据库访问类库用于简化对SQL Server、Oracle、Access等数据源的连接、查询与结果集操作适合需要在VC项目中快速集成数据库功能的中级开发人员。压缩包共3个文件包含C头文件与实现文件.h/.cpp以及一份完整的网页存档.mht说明文档整体仅105KB轻量易用。目前已有124人学习下载。通过提供的ado2.cpp与ado2.h开发者可以直接使用封装好的ADO2类避免手动处理Connection、Command、Recordset等底层对象的繁琐细节配套的MHT文档则保留了原始发布页的详细介绍、示例代码和使用指南便于理解类库的调用方式与扩展思路。整体上这是一套小而实用的ADO封装方案可显著提升VC数据库应用的开发效率。 如果今天你还在翻 ADO 2.20 的类文档大概率不是因为它是新玩意而是因为一套比你工龄还大的系统终于轮到你接手了。ADO 2.20 是微软 ActiveX Data Objects 在 1998 到 2000 年之间的经典版本在 VB6、ASP、VBA 和早期 VC 项目里几乎随处可见。它的核心就是以 ADODB 为命名空间的 COM 类库用一套对象模型把数据库连接、SQL 执行、记录集遍历全部包起来。这篇文章我会直接围绕这些类的职责、上手套路和排错经验展开适合正在维护旧系统、被要求用 VBScript 或 VBA 处理数据库、或者纯粹想弄懂这段技术渊源的人。1. 从 ADO 2.20 聊起这套类到底解决什么问题1.1 ADO 2.20 的江湖地位很多人第一次看到 ADO 2.20 是在 VB6 的工程-引用对话框里那一串Microsoft ActiveX Data Objects 2.x Library让人印象深刻。这个 2.20 版本对应的是 MDAC 2.0/2.1 时代当时 OLEDB 是微软主推的数据访问底层接口ADO 则把 OLEDB 的复杂性包装成上层应用能直接调用的类。相比更早的 DAO 和 RDOADO 的类设计更统一既能连 SQL Server、Oracle也能连 Access、FoxPro甚至还能连非关系型数据源只要有人写了对应的 OLEDB Provider。它的厉害之处不是性能而是一套类打天下。你在 VB6 里写Set conn New ADODB.Connection到了 ASP 页面里写CreateObject(ADODB.Connection)到了 VBA 里同样可以这么干。这种跨宿主的一致性让开发人员只需要学一次对象模型就能在各种微软生态里复用。哪怕放到今天很多老系统里的报表模块、导入导出工具、定时任务底层还是这几个类在撑。1.2 所谓 Class本质是一组 COM 组件的契约讲 ADO 的类必须先理解 COM 的思维。ADODB 里面的 Connection、Command、Recordset并不是普通意义上的编程语言类而是 COM 类每个类都有唯一注册的 ProgID 和 CLSID。你在代码里写CreateObject(ADODB.Connection)系统会去注册表里找到这个 ProgID 对应的 CLSID然后通过 CoCreateInstance 创建实例。这个机制带来的好处是调用方不需要关心底层 DLL 在哪不需要 include 头文件只要目标机器上装了对应的 ADO 版本并完成注册代码就能跑。坏处也显而易见一旦注册表被污染、系统升级导致版本错位、或者 32 位和 64 位组件混装就会出现各种让人抓狂的类错误。我后面会专门讲这类问题。2. 认识 ADO 2.20 的类族谱核心对象的分工2.1 Connection 类连接不是数据库是对话会话我见过不少初学者把 Connection 理解成数据库本身这是一个很常见的误区。Connection 本质是一条到数据库的通道它管理的是连接状态、登录凭据、当前数据库上下文以及事务边界。打开连接就是建立会话关闭连接就是释放通道数据库本身并不会因为连接关闭而消失。ADO 2.20 里Connection 类最重要的几个成员是ConnectionString、Open、Close、Execute、BeginTrans、CommitTrans和RollbackTrans。事务方法尤其值得注意很多人直接用 Execute 执行一串 SQL却忘了包事务一旦中间报错数据就处于半完成状态。正确的做法是先BeginTrans全部成功再CommitTrans有问题就RollbackTrans。2.2 Command 类参数化查询的正确姿势Command 类的定位是准备执行的命令。它可以是一条 SQL 语句也可以是存储过程调用。相比直接用 Connection.Execute 拼接字符串Command 最大的价值在于参数化。假设你要查用户名为admin or 11的记录如果直接拼 SQL 字符串轻则查询错乱重则被 SQL 注入。用 Command 的Parameters.Append或者CreateParameter方法参数值由 ADO 负责转义你的输入就只是数据而不是 SQL 片段。在 ADO 2.20 时代程序员的安全意识普遍不强所以这个类反而是最容易被低估的。Dim cmd As ADODB.Command Set cmd New ADODB.Command cmd.ActiveConnection conn cmd.CommandType adCmdText cmd.CommandText SELECT * FROM users WHERE name ? cmd.Parameters.Append cmd.CreateParameter(name, adVarChar, adParamInput, 50, userName) Dim rs As ADODB.Recordset Set rs cmd.Execute()2.3 Recordset 类游标和锁定的博弈Recordset 是 ADO 里最常被操作的类它代表查询结果集的内存视图。但这个类同时也是配置项最多、最容易写错的地方。打开 Recordset 时你需要同时决定游标类型CursorType和锁定类型LockType这两个参数直接影响性能和数据一致性。游标类型有四种前向游标ForwardOnly、静态游标Static、键集游标Keyset和动态游标Dynamic。其中前向游标性能最好因为它只能往一个方向移动很多 OLEDB Provider 会针对这种游标做流式优化。动态游标最灵活但开销也最大。锁定类型则用来控制并发时对记录的锁定程度从只读ReadOnly到悲观锁定Pessimistic逐级递增。我见过太多人默认使用adOpenDynamic adLockOptimistic来读全部数据结果就是把几万条记录全部加载到内存然后抱怨ADO 太慢。其实大部分报表场景只需要一个adOpenForwardOnly adLockReadOnly性能差距立竿见影。2.4 辅助类Fields、Properties、Errors、Parameters除了三大核心类ADO 2.20 的对象模型里还包含几个集合类官方也把它们作为类来对待。Fields 集合挂在 Recordset 下代表每个字段的元数据和值Properties 集合挂在 Connection、Command、Recordset、Field 下存放各自的动态属性Errors 集合挂在 Connection 下记录最近一次操作产生的错误Parameters 集合挂在 Command 下保存参数信息。这些辅助类在实际维护中同样重要。排查问题的时候我会先遍历conn.Errors看看 Provider 返回的原始错误信息有时候比 ADO 抛出的标准异常有用得多。Properties 里的ActiveConnection、Source等属性也能帮你快速判断一个 Recordset 的来源。3. 用 Class 连一次库从连接字符串到逐行读取3.1 连接字符串的选型与拼接写 ADO 2.20 代码第一步永远是连接字符串。连接字符串没有通用格式它取决于你选的 Provider。Access 老库常用Microsoft.Jet.OLEDB.4.0SQL Server 2000 时代常用SQLOLEDB新一点的环境会选SQLNCLI或者MSOLEDBSQL。选 Provider 的底层逻辑是你需要的是谁去对话以及对方支持什么协议。一个常见的错误是在 64 位系统里用默认的 Jet Provider 连 Access结果 Jet 4.0 没有 64 位版本程序直接报未找到提供程序。解决办法是改装 ACE Provider也就是Microsoft.ACE.OLEDB.12.0并且要注意 ACE 也有 32/64 位版本之分。连接字符串里的Data Source路径最好用绝对路径旧程序如果用了相对路径工作目录一变就找不到库了。3.2 打开、操作、关闭一个完整的生命周期用 ADO 的类最忌讳的是只开不关。COM 对象持有的是非托管资源如果 Recordset 没关闭、连接没释放内存和数据库连接池很快就会被占满。规范的流程是创建 Connection设置 ConnectionString调 Open。用 Command 或者 Connection.Execute 得到 Recordset。遍历 Recordset处理每一行Fields里的数据。处理完先rs.Close再Set rs Nothing。最后conn.CloseSet conn Nothing。这里有个很多人忽略的细节Set rs Nothing和rs.Close并不是一回事。Close 只是关闭游标COM 对象还在内存里随时可以重新打开只有Set rs Nothing才会真正释放对象。所以在循环里创建 Recordset 时要确保每次都释放干净否则长时间跑下来内存会持续上涨。3.3 逐行读取的性能习惯逐行读取 Recordset 时标准动作是Do Until rs.EOF加rs.MoveNext。这里有一个性能关键尽量用字段索引而不是字段名去访问Fields。比如rs.Fields(0).Value比rs.Fields(name).Value快因为字符串查找字段名需要遍历字段集合。在几十万行的循环里这个差距是肉眼可见的。另一个习惯是提前把整个结果集需要的字段名记录到变量避免在循环里重复解析。尤其在 VBScript 这种解释型环境里每行少做几次调用整体耗时能下降不少。如果你只是想统计行数根本不用遍历可以用rs.RecordCount但要注意这取决于游标类型前向游标不一定返回准确值。4. 报错排查Com 类注册与版本错乱的常见坑4.1 Class not registered 的完整排错链路我在维护旧系统时最常见的一句话就是Class not registered或者未找到提供程序。这类问题可以从根上拆成三步排查。第一步确认 ADO 本身有没有装好。打开命令行运行regsvr32 msado15.dll如果是正常的说明 ADO 运行时还在。第二步确认 Provider 对应 DLL 是否注册。比如 Jet Provider 的msjetoledb.dll、ACE Provider 的ACEDAO.DLL如果这些文件存在但注册表里找不到对应的 CLSID 或 ProgID就需要重装对应的数据访问组件。第三步确认程勋跑在哪个位数通道里。注册表里 COM 类的位置分为 32 位和 64 位视图32 位程序去 64 位注册表里找类是找不到的。4.2 32 位与 64 位的“看不见的墙”这个问题在 ADO 2.20 时代尤为突出。旧系统很多是用 VB6 编译的 32 位程序而新部署的服务器往往是 64 位系统默认 IIS 的应用池也可能跑 64 位。这时候你会发现同一个 Provider32 位能注册64 位却不行或者反过来64 位的 Access Database Engine 装了32 位的 VBA 工具就是找不到。遇到这类问题先说结论你需要在对应的位数通道下安装对应的组件。比如 64 位 IIS 要连 Access得装 64 位 ACE32 位程序在 64 位系统上跑得装 32 位 ACE然后再想办法让 IIS 应用池 启用 32 位应用程序 为 True。不要迷信某一种方式解决问题前先搞清楚进程位数。4.3 引用丢失VB 工程中对 ADO 类库的版本锁定在 VB6 或 VBA 里如果你在工程中使用了早期绑定也就是勾选了Microsoft ActiveX Data Objects 2.0 Library那么编译出来的程序会记住这个版本对应的类型库 GUID。当目标机器上没有这个版本只有更高版本时程序在编译期没问题运行期却可能弹出未找到方法或数据成员。解决这类问题有两个方向一是把代码全部改成后期绑定统一用CreateObject(ADODB.Connection)这样程序在运行时才去解析类目标机器只要有兼容版本就能跑二是对目标机器的类型库做兼容性映射但这种方法很不可控我强烈不建议。改成后期绑定虽然会牺牲一点点性能但换来的是大得多的部署自由度。5. 给现代开发者的迁移建议5.1 从 ADO 的类到 ADO.NET/DB-API 的对应关系如果你已经熟悉 ADO 2.20 的类模型转到现代技术栈其实很顺手。ADO.NET 里的SqlConnection、SqlCommand、SqlDataReader基本就是Connection、Command、Recordset的对应版本只不过SqlDataReader是只进只读的流式读取器更像adOpenForwardOnly adLockReadOnly的 Recordset。Python DB-API 里的connection、cursor、execute、fetchall也遵循同样的心智模型。理解这套对应关系对迁移旧代码特别有用。老系统里的遍历逻辑可以原样翻译成while reader.Read()或者for row in cursor.fetchall()事务逻辑从BeginTrans/CommitTrans改成新框架里的事务对象。迁移时注意两类差异一是参数占位符不一样ADO 用?SQL Server 的 ADO.NET 用namePython 的很多驱动用%s二是错误处理模型不一样ADO 依赖 Connection 的 Errors 集合现代框架基本是抛异常所以 try-catch 的粒度要重新设计。5.2 遗留系统维护的务实建议如果你和我一样手里压着几个 ADO 2.20 的老系统不要急着全部重写。我的实际经验是先把连接字符串集中管理把创建、打开、关闭、释放这些动作统一封装成公共函数减少散落的CreateObject和Set Nothing代码。然后重点检查所有执行 SQL 的地方把字符串拼接改成 Command 参数化这是老系统性价比最高的安全改造。之后再看有没有可以替换成的现代组件比如把 Access 后端迁到 SQL Server Express同时保持 ADO 接口不变应用层代码甚至不用大改。最后分享一个我自己踩过的坑老服务器上跑得好好的 ADO 程序迁移到新机器后频繁报错查了整整一天最后发现新机器上装着一个精简版 Office它把某些旧版 ODBC/OLEDB 驱动覆盖了。解决方案也很粗暴完全卸载重新安装完整的 MDAC 运行库和对应的 Provider。Windows 10/11 自带 ADO 组件但不要轻易相信默认配置永远要在目标机器上做一次最小化连接测试。老堆栈有老堆栈的脾气摸准了就好办。本文还有配套的精品资源点击获取