1. 从一次“记录数永远不对”的排查说起:GetRecordCount 返回 -1 到底卡在哪
如果你在用 VC6 或 VS 系列写 MFC 程序,通过 ADO 访问 Access2000 的.mdb文件,然后调用m_pRecordset->GetRecordCount()想拿到记录条数,结果发现它要么返回-1,要么返回一个明显偏大、偏小的值,那你不是一个人。这个坑我踩过,而且踩得挺深。
先说清楚GetRecordCount是什么。它是 ADORecordset对象的一个属性,用来返回当前记录集中的记录数量。听起来很简单,但它的返回值高度依赖两个东西:游标类型(CursorType)和游标位置(CursorLocation)。如果游标是只进游标(adOpenForwardOnly),那RecordCount基本就是废的,返回-1是常态,因为驱动根本没打算帮你数总数。而 Access2000 这种老数据库,配合不同版本的msado15.dll,行为差异会更大。
适合谁看这篇?正在维护老 MFC 工程、需要兼容 Access2000、又不想大改数据访问层的同学。核心检索词就是GetRecordCount、msado15.dll、access2000、adOpenStatic、RecordCount。我会把连接字符串、游标配置、三步验证动作都写成可复制的片段,你照着改就能定位到底是游标问题、DLL 版本问题,还是 SQL 本身的问题。
典型场景是这样的:你要在保存数据前判断某个ProductNum是否已存在,存在就更新,不存在就插入。代码大概长这样:
strSql.Format("SELECT * FROM TestData WHERE ProductNum='%s'", productNum); hbr = m_pRecordset->Open(_bstr_t(strSql), m_pConnection.GetInterfacePtr(), adOpenDynamic, adLockOptimistic, adCmdText); RecordCount = m_pRecordset->GetRecordCount();问题就出在这里:adOpenDynamic是动态游标,很多 Provider 对动态游标的RecordCount支持很差,返回-1或者一个不可信的值。你换成adOpenStatic有时也不灵,因为游标位置没设对,或者msado15.dll版本和 Access2000 的 Jet 引擎不匹配。下面我按排查路径一步步拆。
2. TaoToken 前置准备:把模型对话和接入文档先跑通
在正式改代码之前,我建议先把排查思路理清楚,而不是盲目替换 DLL。这里可以借助 TaoToken 的模型对话能力,把你遇到的报错、连接字符串、游标参数贴进去,让它帮你分析可能的原因。TaoToken 是一个聚合多种大模型的 API 平台,你可以用它来辅助排查这类老代码问题,也可以用它做代码润色和文档整理。
具体怎么用?先打开模型对话页面,地址是:
https://taotoken.net/api如果你需要看接入文档,了解 Base URL、Key、Model ID 怎么填,可以访问:
https://taotoken.net/api-keys以及文档页:
https://taotoken.net/doc注意,TaoToken 的 API 地址是https://taotoken.net/api,这个不加 UTM 参数,直接用于代码里的 Base URL。而官网首页带 UTM 的是:
https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=为什么要先做这一步?因为排查GetRecordCount不准的问题,往往需要对比多个方案:换游标、换 DLL、换 SQL 写法。你可以把每种方案的代码片段丢给模型,让它帮你检查参数是否写对。比如你写adOpenStatic但CursorLocation还是默认的adUseServer,模型会提醒你静态游标通常要配合客户端游标才能可靠返回RecordCount。
另外,如果你长期要做这类老工程维护和 Agent 辅助编码,可以考虑 Coding Plan,地址是:
https://taotoken.net/coding-plan它适合需要长期编码辅助的场景。不过这篇的重点还是排查路径,TaoToken 在这里的角色是帮你快速验证思路、生成对比代码,而不是替代你的调试过程。
前置准备还包括一件事:确认你的工程里msado15.dll的引入路径。很多老工程在StdAfx.h里写的是:
#import "C:\Program Files\Common Files\System\ado\msado15.dll" no_namespace rename("EOF","adoEOF")而有的工程为了避开系统目录权限问题,会把 DLL 拷到别的路径再引入。这个路径差异,后面会直接影响GetRecordCount的行为。
3. 可复制配置:连接字符串、游标类型与 msado15.dll 引入片段
这一节给你可以直接抄的配置。先看连接字符串。Access2000 用的是 Jet 引擎,Provider 要写Microsoft.Jet.OLEDB.4.0:
CString ConnectString; ConnectString.Format("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=%s", lpszFile); hbr = m_pConnection->Open((_bstr_t)ConnectString, "", "", adModeUnknown);连接对象创建后,关键一步是设置CursorLocation。如果你想让RecordCount可靠,通常要设成客户端游标:
m_pConnection->CursorLocation = adUseClient;然后是 Recordset 的打开方式。把adOpenDynamic换成adOpenStatic,并且锁类型用adLockReadOnly或adLockOptimistic都行,但游标必须是静态或键集:
hbr = m_pRecordset->Open(_bstr_t(strSql), m_pConnection.GetInterfacePtr(), adOpenStatic, adLockOptimistic, adCmdText);如果你用的是m_pSet(CRecordset 派生类),那要在Open之前设置:
m_pSet->CursorType = adOpenStatic; m_pSet->CursorLocation = adUseClient;接下来是msado15.dll的引入。原始工程里可能是这样:
#import "C:\Program Files\Common Files\System\ado\msado15.dll" no_namespace rename("EOF","adoEOF")如果你怀疑是 Win7 自带的msado15.dll对 Access2000 支持不好,可以从一个确认正常的旧系统里拷贝一份,放到独立目录,然后改引入路径:
#import "C:\Program Files\Common Files\msado15.dll" no_namespace rename("EOF","adoEOF")注意,这里路径变了,DLL 文件也换了。这个操作在原始案例里是解决问题的关键一步。但我要提醒你:替换系统 DLL 有风险,建议只把旧 DLL 放在工程私有目录,通过引入路径指定,不要直接覆盖系统目录里的文件。
为了让你对照参数,我列一个表:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| Provider | Microsoft.Jet.OLEDB.4.0 | Access2000 专用 |
| CursorLocation | adUseClient | 客户端游标,RecordCount 更可靠 |
| CursorType | adOpenStatic | 静态游标,支持 RecordCount |
| LockType | adLockOptimistic | 按需选择,不影响计数 |
| CommandType | adCmdText | 直接执行 SQL 文本 |
还有一个细节:如果你的 SQL 是SELECT *,而表里字段很多,静态游标会把整个结果集拉到客户端,内存开销大。如果只是判断存在性,更好的做法是用SELECT COUNT(*),这个后面验证环节会讲。
4. 验证请求与成功结果:三步动作确认 RecordCount 是否可信
改完配置别急着下结论,按下面三步验证。
第一步:切换游标类型对比。先用adOpenDynamic跑一次,打印GetRecordCount();再换成adOpenStatic+adUseClient跑一次,打印同样的值。如果第一次返回-1或异常大值,第二次返回正确条数,那问题就在游标配置。
long nCount = m_pRecordset->GetRecordCount(); TRACE("RecordCount = %ld\n", nCount);第二步:对比SELECT COUNT(*)。单独开一个 Recordset,执行:
strSql = "SELECT COUNT(*) FROM TestData WHERE ProductNum='...'";拿到数据库层面的真实条数,和GetRecordCount()的结果比对。如果两者不一致,说明游标返回的计数不可信;如果一致,说明配置生效了。
第三步:记录实际遍历条数。用while(!m_pRecordset->adoEOF)循环遍历,自己累加计数,最后和GetRecordCount()对比。这一步最笨但最可靠,能排除游标缓存导致的偏差。
long nReal = 0; while (!m_pRecordset->adoEOF) { nReal++; m_pRecordset->MoveNext(); } TRACE("Real count = %ld\n", nReal);成功的结果应该是:GetRecordCount()返回值等于SELECT COUNT(*)的结果,也等于实际遍历条数。如果三者一致,说明游标、DLL、SQL 都没问题。如果GetRecordCount()还是不对,但实际遍历条数正确,那说明你的业务逻辑可以改用遍历计数,或者继续排查 DLL 版本。
我实测下来,在 Win7 上如果用的是系统自带的msado15.dll,配合 Access2000,adOpenStatic有时仍然返回偏大的值。换成旧版 DLL 后,GetRecordCount()才和SELECT COUNT(*)对上。所以第三步的遍历计数非常关键,它能帮你判断到底是计数属性坏了,还是数据本身有问题。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth 报错对照
虽然这篇主要讲 ADO 和 Access2000,但你在用 TaoToken 辅助排查时,可能会遇到一些接入层的报错。这里列几个常见的,方便你对照。
401 Unauthorized。通常是 API Key 没填对,或者 Key 过期。检查你的请求头里Authorization: Bearer <你的Key>是否正确。如果你在代码里把 Key 写死,注意别把空格或换行带进去。
local proxy failed。这个报错一般出现在本地网络配置或代理设置上。如果你在代码里设置了 HTTP 代理,但代理不可用,就会报这个。检查你的WinINet或WinHttp配置,确认没有残留的代理设置。
reading choices 相关报错。如果你在调用模型接口时,返回体里choices字段读取失败,通常是响应格式和预期不符。检查你的 JSON 解析代码,确认choices[0].message.content路径正确。
OAuth 报错。如果你用的是需要 OAuth 的接入方式,报错通常是 token 过期或 scope 不对。重新走一遍授权流程,确认回调地址和 Key 匹配。
回到 ADO 本身,还有一个常见错:m_pRecordset->MoveLast()在记录不存在时直接报错。原始案例里提到过这个。原因是空记录集上调用MoveLast会触发adErrNoCurrentRecord。所以不要用MoveLast来“逼”出RecordCount,正确做法还是设对游标。
另外,如果你在StdAfx.h里改了msado15.dll的引入路径,记得清理工程重新编译,否则可能还是链接到旧的类型库。清理Debug和Release目录下的.tlh、.tli文件,再重新生成。
还有一个坑:CursorLocation必须在Open之前设置,连接对象和记录集对象都是。如果你在Open之后才改,不生效。
6. 语义一致 CTA:把排查思路沉淀成可复用的接入配置
排查完GetRecordCount不准的问题,你会发现核心就三件事:游标类型、游标位置、DLL 版本。把这三件事固定成一套可复用的配置,以后遇到类似的老数据库访问问题,直接套用就行。
如果你想把这类排查过程沉淀成文档,或者让模型帮你生成对比代码,可以用 TaoToken 的模型对话:
https://taotoken.net/api需要管理 Key 就去 API Keys 页面:
https://taotoken.net/api-keys接入细节看文档:
https://taotoken.net/doc长期做编码辅助和 Agent 开发,可以了解 Coding Plan:
https://taotoken.net/coding-plan最后给你一个实用技巧:在工程里封装一个GetRealRecordCount函数,内部先用SELECT COUNT(*)拿真实条数,再决定是否打开记录集。这样既避开了GetRecordCount的坑,又不会因为静态游标拉取全表而浪费内存。对于 Access2000 这种老库,SELECT COUNT(*)的执行速度通常比打开静态游标快得多。