简介ADO2.20 Class 是一套面向 Visual C 开发者的 ActiveX Data Objects 类库最初由非中文开发者在 codeproject.com 发布旨在简化 VC 程序对 SQL Server、Oracle、Access 等数据源的访问让开发者无需深入底层 ADO 接口即可快速集成数据库操作。资源包共 3 个文件包含 1 个 cpp 实现文件、1 个 h 头文件以及 1 个 mht 说明文档压缩包约 105KB体积轻巧便于直接嵌入现有工程。其中头文件提供类定义与接口声明cpp 文件封装了 Connection、Command、Recordset 等核心对象的常用操作mht 文档则保留了原版使用说明与示例参考。目前已有 126 人学习下载适合不熟悉 ADO 或需要快速开发数据库应用的 VC 开发者通过实例化封装类即可完成连接、查询与结果集遍历将精力集中在业务逻辑上。1. ADO2.20 Class一份被低估的数据库访问封装资源如果你维护过五年前甚至更早的遗留系统大概率见过这样的代码连接字符串硬编码在业务逻辑里Recordset对象满天飞异常处理全靠On Error Resume Next兜底。ADO2.20 Class 就是针对这种场景的一份封装资源它把 ADO 2.20 时代的连接管理、命令执行、记录集遍历和错误捕获收拢成一套可复用的类模块。这不是什么新潮的 ORM 框架而是一份能让老系统少出玄学问题的工程化封装。适合谁正在维护经典 ASP 或早期 VB 项目、需要在不引入外部依赖的前提下把数据库访问层理清楚的从业者。它解决的核心问题只有一个让散落各处的 ADO 调用变成有边界、有日志、有统一出口的模块。2. 拆开这份资源类模块结构与连接管理逻辑2.1 资源里到底有什么这份资源的主体是一组类模块文件通常包含连接管理类、命令执行类和记录集处理类三个核心部分。连接管理类负责持有ADODB.Connection对象对外暴露打开、关闭和重连的方法命令执行类封装ADODB.Command处理参数化查询和存储过程调用记录集处理类则把ADODB.Recordset的遍历、字段读取和转数组操作收拢起来。常见做法是每个类对应一个.cls文件配套一个标准模块存放全局配置和工具函数。资源里一般还会带一个示例页面或测试入口用来验证连接是否正常、查询是否返回预期结果。文件格式以文本类源码为主直接导入对应的开发环境即可不需要编译或安装。2.2 连接管理为什么值得单独抽一层很多人觉得 ADO 连接就是一行Server.CreateObject(ADODB.Connection)的事没必要封装。但实际维护中连接泄漏、超时设置不一致、连接字符串散落各处才是真正的血泪经验来源。把连接管理抽成类之后至少有三个好处第一连接字符串集中在一处换数据库或改认证方式时不用全局搜索替换第二连接超时和命令超时可以在类初始化时统一设定避免某个页面卡死拖垮整个站点第三连接对象的生命周期有了明确边界配合Class_Terminate可以做到异常时自动释放。下面是一个连接管理类的典型骨架我一般会这样写 clsDbConnection.cls Private m_Conn As ADODB.Connection Private m_ConnString As String Private Sub Class_Initialize() Set m_Conn New ADODB.Connection m_Conn.ConnectionTimeout 15 m_Conn.CommandTimeout 30 End Sub Public Property Let ConnectionString(ByVal value As String) m_ConnString value End Property Public Function Open() As Boolean On Error GoTo ErrHandler If m_Conn.State adStateOpen Then Open True Exit Function End If m_Conn.Open m_ConnString Open True Exit Function ErrHandler: 记录错误日志不向上抛出原始错误 LogError Open, Err.Number, Err.Description Open False End Function Public Property Get RawConnection() As ADODB.Connection Set RawConnection m_Conn End Property 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这段代码的关键点在于Class_Terminate里的清理逻辑。VB 类的析构在对象引用计数归零时触发只要调用方没有长期持有这个连接对象连接就能在页面结束时自动关闭。ConnectionTimeout和CommandTimeout分开设置也很重要前者管建立连接后者管查询执行很多超时问题其实是命令超时而不是连接超时。2.3 命令执行与参数化查询的落地方式命令执行类要解决的核心问题是参数化。拼接 SQL 字符串在遗留系统里太常见了但它是注入风险的根源也是类型转换错误的温床。ADO 的Command对象支持Parameters.Append配合CreateParameter可以做到类型明确的参数传递。 clsDbCommand.cls Public Function ExecuteNonQuery(ByVal sql As String, _ ByVal params As Variant) As Long Dim cmd As ADODB.Command Set cmd New ADODB.Command Set cmd.ActiveConnection m_Conn cmd.CommandType adCmdText cmd.CommandText sql Dim i As Long For i LBound(params) To UBound(params) cmd.Parameters.Append cmd.CreateParameter( _ p i, adVarWChar, adParamInput, 255, params(i)) Next i cmd.Execute , , adExecuteNoRecords ExecuteNonQuery cmd.Parameters(p0).Value Set cmd Nothing End Function参数说明adVarWChar对应 Unicode 字符串adParamInput表示输入参数长度 255 是常见默认值实际使用时按字段长度调整。adExecuteNoRecords告诉 ADO 这次执行不返回记录集能省掉不必要的开销。注意ExecuteNonQuery的返回值这里取的是第一个参数的值适合存储过程带输出参数的场景如果是普通 INSERT应该改用RecordsAffected属性。2.4 记录集转数组的实用技巧记录集处理类里最实用的功能是GetRows。它一次性把结果集拉成二维数组断开记录集后仍然可以遍历对分页和缓存场景很友好。Public Function QueryToArray(ByVal sql As String) As Variant Dim rs As ADODB.Recordset Set rs m_Conn.Execute(sql) If rs.EOF And rs.BOF Then QueryToArray Array() Else QueryToArray rs.GetRows() End If rs.Close Set rs Nothing End FunctionGetRows返回的数组第一维是字段第二维是行。遍历时用arr(fieldIndex, rowIndex)访问。空结果集要单独判断否则GetRows返回空数组调用方直接取下标会翻车。这个细节在资源自带的示例里通常有体现但很多人导入后不看示例直接套自己的旧代码结果就在空数据上栽跟头。3. 从导入到跑通一份可复现的接入流程3.1 环境准备与文件放置接入之前先确认运行环境。ADO 2.20 对应的组件版本在多数 Windows 服务器上默认可用不需要额外安装。开发环境里如果用的是经典 ASP把.cls文件放在站点目录下通过!--#include fileclsDbConnection.cls--引入如果是 VB6 或 VBA 项目直接在工程里添加类模块。常见做法是建一个lib目录专门放这些类文件再建一个config目录放连接字符串配置。连接字符串不要写死在类里用标准模块的全局变量或外部配置文件读取。 modConfig.bas Public Const DB_CONN_STRING ProviderSQLOLEDB;Data Source127.0.0.1; _ Initial CatalogTestDb;User Idapp_user;Passwordapp_pass;提示连接字符串里的密码如果明文存放至少要把配置文件放在 Web 根目录之外避免被直接下载。3.2 最小可运行示例导入类文件后写一个最简单的测试页面验证链路是否通。!-- test_connection.asp -- % Dim db, result Set db New clsDbConnection db.ConnectionString DB_CONN_STRING If db.Open() Then result db.QueryToArray(SELECT TOP 5 id, name FROM users) If IsArray(result) Then Dim i For i 0 To UBound(result, 2) Response.Write result(0, i) - result(1, i) br Next Else Response.Write 查询返回空结果 End If Else Response.Write 连接失败检查日志 End If Set db Nothing %这段代码覆盖了连接、查询、遍历、释放四个环节。如果页面输出正常说明类模块导入和连接配置都没问题。如果连接失败先看日志里记录的错误号和描述常见的是 Provider 未注册或认证失败。3.3 参数化查询的调用方式验证完基础连接后把业务查询改成参数化调用。以用户登录为例Dim cmd, params Set cmd New clsDbCommand Set cmd.Connection db.RawConnection params Array(userName, userPass) Dim affected affected cmd.ExecuteNonQuery( _ UPDATE users SET last_login GETDATE() WHERE name ? AND pass ?, _ params)注意这里的?占位符顺序要和params数组顺序一致。ADO 的参数绑定是按位置匹配的不是按名字。如果 SQL 里用了命名参数如name在adCmdText模式下仍然按位置绑定这一点和 SQL Server 原生 T-SQL 的行为不同容易踩坑。3.4 错误日志与排查入口资源里通常会带一个简单的日志函数把错误写入文本文件或数据库表。我一般会把它改成按天分文件避免单个日志文件过大。Public Sub LogError(ByVal funcName As String, _ ByVal errNum As Long, ByVal errDesc As String) Dim fso, ts, logPath logPath Server.MapPath(/logs/ado_error_ _ Year(Now) Month(Now) Day(Now) .log) Set fso CreateObject(Scripting.FileSystemObject) Set ts fso.OpenTextFile(logPath, 8, True) ts.WriteLine Now() | funcName | _ errNum | errDesc ts.Close Set ts Nothing Set fso Nothing End Sub日志路径要确保 IIS 或应用程序池账号有写入权限否则日志写不进去排查时就成了黑匣子。权限问题在 Windows 环境下比代码问题更常见尤其是部署到新服务器时。4. 避坑与排查ADO 封装里最容易翻车的五个点4.1 连接状态判断失效现象明明数据库正常Open()却返回 False日志里没有错误记录。原因ADODB.Connection.State在某些 Provider 下返回的不是adStateOpen而是其他值。另外如果连接字符串里指定的 Provider 和实际安装的版本不匹配Open会直接抛错但被On Error吞掉。解决在Open()里把Err.Number和Err.Description都写进日志不要只写一句“连接失败”。同时用m_Conn.Provider属性确认实际使用的 Provider 名称。4.2 参数顺序错位导致查询结果异常现象参数化查询不报错但返回的数据不对或者 UPDATE 影响了错误的行。原因ADO 在adCmdText模式下按位置绑定参数SQL 里?的顺序和Parameters.Append的顺序必须严格一致。如果中间插入了条件分支动态拼接 SQL顺序很容易乱。解决动态 SQL 场景下把参数收集到一个数组里最后统一 Append。不要边拼 SQL 边 Append 参数。4.3 记录集未关闭导致连接池耗尽现象站点运行一段时间后所有页面都卡住重启应用池后恢复。原因Recordset对象没有显式Close或者GetRows之后没有释放记录集。ADO 的连接池有上限记录集持有连接不释放池子很快见底。解决所有Execute返回的记录集用完立即Close并Set Nothing。GetRows之后记录集就可以关了数组和记录集之间没有依赖关系。4.4 超时设置不生效现象设置了CommandTimeout但慢查询还是卡很久。原因CommandTimeout只对Command对象生效如果直接用Connection.Execute用的是连接的CommandTimeout。另外某些 Provider 会忽略这个设置实际超时由数据库端控制。解决统一走Command对象执行查询不要混用Connection.Execute。同时在数据库端也设置查询超时作为兜底。4.5 类模块在页面间共享时的状态污染现象A 页面修改了连接字符串B 页面跟着变了。原因如果类模块被放在 Application 或 Session 作用域实例是共享的。连接字符串作为实例属性被修改后所有引用该实例的页面都会受影响。解决连接管理类不要放 Application 或 Session每次请求新建实例。连接池本身已经在底层做了复用不需要在应用层再共享连接对象。5. 进阶用法把 ADO2.20 Class 改造成带重试的连接层5.1 为什么需要重试数据库连接偶尔抖动是常态尤其是跨网段访问或数据库服务器在做维护时。一次Open失败就返回错误页面用户体验很差。给连接层加一个简单的重试逻辑能挡掉大部分瞬时故障。5.2 重试逻辑的实现在连接管理类里加一个OpenWithRetry方法Public Function OpenWithRetry(ByVal maxRetry As Long, _ ByVal intervalMs As Long) As Boolean Dim attempt As Long For attempt 1 To maxRetry If Me.Open() Then OpenWithRetry True Exit Function End If If attempt maxRetry Then 等待间隔后重试 Dim t As Single t Timer Do While Timer t intervalMs / 1000 DoEvents Loop End If Next attempt OpenWithRetry False End Function参数说明maxRetry建议设 3太多会拖长页面响应时间intervalMs建议 200 到 500 毫秒太短没意义太长用户等不及。DoEvents在 ASP 环境下行为不稳定如果是在纯 ASP 里用可以换成Server.ScriptTimeout配合简单的空循环或者直接去掉等待逻辑靠重试次数本身拉开时间间隔。5.3 重试的边界与验证重试不是万能的。认证失败、数据库不存在、SQL 语法错误这类问题重试多少次都一样。所以重试之前要先判断错误类型只对连接超时和网络类错误重试。验证方法很简单在测试环境里把数据库服务停掉观察页面是否在重试后返回友好提示而不是直接抛 500 错误。然后再把数据库启动确认下一次请求能正常恢复。错误类型是否重试判断依据连接超时是Err.Number 为 -2147467259认证失败否Err.Number 为 -2147217843数据库不存在否Err.Description 含 Cannot open database网络中断是Err.Number 为 -2147467259 且 Description 含 network这张表是我自己在项目里总结的不同 Provider 的错误号可能有差异落地时先用日志把实际错误号打出来再对照调整。5.4 一个我踩过的坑早期我把重试逻辑写在了业务层每个查询方法里都套一层循环结果代码重复不说还出现过重试期间连接对象被其他线程修改的情况。后来统一收到连接管理类里业务层只调OpenWithRetry世界就清净了。从那以后我每次封装数据库访问层都强制把重试、日志、超时这三件事放在最底层不让它们渗透到业务代码里。希望帮到你。本文还有配套的精品资源点击获取