简介这份面向VC开发者的ADO实例包围绕Command对象在数据库访问中的实际用法展开适合已掌握Connection基础连接、希望进一步理解存储过程调用与参数化查询的读者对照学习。包内共11个文件以3个cpp源文件和3个h头文件为核心配合vcxproj、sln、filters等Visual Studio工程配置另有1个txt说明压缩包约8KB结构精简便于直接打开解决方案梳理代码组织。示例工程TestAdo覆盖Command对象的创建与初始化、ActiveConnection与CommandText属性设置、CommandType选择以及通过Parameters集合追加输入参数、调用存储过程、使用Execute获取结果集或影响行数等环节并包含异常处理思路。读者可编译运行后逐段对照理解VC环境下ADO命令执行的完整链路也可将其作为小型数据库操作范例迁移到自己的项目中。目前已有528人学习适合用作入门后的实战参考。1. 从 Connection 到 CommandAdo 实例里最该先拆的那个对象VCAdo 的实例代码里Connection 往往先被写出来真正决定数据访问层是否好维护的却是 Command 对象。_CommandPtr 把 SQL 文本、存储过程、参数集合和返回记录集收在一个对象里适合做参数化查询、批量更新和带输出参数的存储过程调用。它不负责建立连接也不负责遍历结果但把“执行什么、带什么参数、返回什么”这件事说清楚了。对正在维护 VC 老项目、又不想把 SQL 拼接散落在各处的开发者这份 Ado 实例的价值就在这里先把 Command 的对象模型和生命周期拆明白再谈封装和复用。适合已经能连上数据库、但参数化查询和存储过程调用总是写不顺的人。2. 接入 _CommandPtr从 #import 到 COM 初始化的最小闭环2.1 Command 对象在 ADO 里的位置不是连接也不是结果集ADO 的对象模型里Connection 管连接Command 管命令Parameter 管参数Recordset 管结果。很多 VCAdo 代码把 SQL 直接塞进Connection::Execute短脚本能跑一旦遇到带参数的分页查询、存储过程输出参数、事务里的多条更新就会开始散SQL 字符串拼接、参数顺序靠人工记、错误处理到处复制。Command 对象解决的正是这类“命令定义”问题它把CommandText、CommandType、Parameters和ActiveConnection收在一起执行时再决定返回记录集还是影响行数。我一般把 Command 用在三个场景需要参数化查询的列表页和详情页需要调用存储过程并读取输出参数、返回值的业务动作需要在事务里复用同一条命令模板的批量操作。反过来如果只是一次性执行一条固定 SQL、不关心参数和复用直接Connection::Execute更省事但这类代码一旦多起来后面排查 SQL 注入和类型转换问题会很痛苦。_CommandPtr是#import生成的智能指针包装不是手写类它的价值在于引用计数和_com_error异常路径而不是替代数据库设计。2.2 #import 导入 msado15.dll 与 _CommandPtr 的生成规则VC 里用 ADO常见做法是先导入 ADO 的类型库。#import会生成.tlh和.tli两个中间文件里面就有_ConnectionPtr、_CommandPtr、_RecordsetPtr、_ParameterPtr这些智能指针。no_namespace让这些类型直接落在全局命名空间少写ADODB::rename(EOF, adoEOF)和rename(BOF, adoBOF)是为了避开 MFC 或其它头文件里的EOF宏冲突。路径按机器上的实际位置调整不要硬编码到某台机器的私有目录。#import C:\Program Files\Common Files\System\ado\msado15.dll \ no_namespace rename(EOF, adoEOF) rename(BOF, adoBOF) // _CommandPtr 由 #import 生成CreateInstance 时才真正创建 COM 对象 _CommandPtr pCmd; pCmd.CreateInstance(__uuidof(Command));逻辑说明第一行把 ADO 类型库导入当前编译单元生成智能指针和枚举常量CreateInstance(__uuidof(Command))创建 Command 对象实例失败会抛_com_error。参数说明no_namespace影响类型名写法rename只改生成代码里的枚举名不改数据库里的字段名。注意#import路径里的空格和反斜杠在 C 字符串里要按编译器规则处理常见写法是用双反斜杠或把路径放进项目附加包含目录。另一个容易忽略的点是CoInitialize或CoInitializeEx必须在创建任何 ADO 对象之前调用MFC 项目里常用AfxOleInit()控制台项目里用CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)。2.3 最小可编译闭环CoInitialize、Connection、Command、Recordset下面这段代码把“连上数据库、用 Command 执行一条查询、遍历 Recordset、按顺序释放”放在一个main里。连接字符串里的库名、用户名、密码都是示例值实际替换成自己的测试库。CommandType用adCmdText表示CommandText是一条 SQL 文本Execute的第一个参数传NULL表示不关心影响行数第二个参数传NULL表示参数已经通过Parameters集合绑定。#include windows.h #include comdef.h #include iostream #import C:\Program Files\Common Files\System\ado\msado15.dll \ no_namespace rename(EOF, adoEOF) rename(BOF, adoBOF) int main() { HRESULT hr CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); if (FAILED(hr)) { std::cerr CoInitializeEx failed std::endl; return -1; } _ConnectionPtr pConn; _CommandPtr pCmd; _RecordsetPtr pRs; try { pConn.CreateInstance(__uuidof(Connection)); pConn-ConnectionString _bstr_t( LProviderSQLOLEDB;Data Source127.0.0.1;Initial CatalogDemoDB; LUser IDdbuser;PasswordPssw0rd;); pConn-ConnectionTimeout 5; pConn-Open(_bstr_t(L), _bstr_t(L), _bstr_t(L), adConnectUnspecified); pCmd.CreateInstance(__uuidof(Command)); pCmd-ActiveConnection pConn; pCmd-CommandType adCmdText; pCmd-CommandText _bstr_t( LSELECT TOP 5 UserId, UserName FROM dbo.Users ORDER BY UserId); pRs pCmd-Execute(NULL, NULL, adCmdText); while (!pRs-adoEOF) { _variant_t vId pRs-Fields-Item[_variant_t((long)0)]-Value; _variant_t vName pRs-Fields-Item[_variant_t((long)1)]-Value; std::wcout Lid (long)vId L, name (wchar_t*)_bstr_t(vName) std::endl; pRs-MoveNext(); } } catch (_com_error e) { std::cerr HRESULT0x std::hex e.Error() std::endl; std::cerr Message (char*)e.ErrorMessage() std::endl; if (e.Description().length() 0) std::cerr Description (char*)e.Description() std::endl; } if (pRs ! NULL pRs-State ! adStateClosed) pRs-Close(); pRs NULL; if (pCmd ! NULL) pCmd-ActiveConnection NULL; pCmd NULL; if (pConn ! NULL pConn-State ! adStateClosed) pConn-Close(); pConn NULL; CoUninitialize(); return 0; }逻辑说明ConnectionString里Provider指定 OLE DB 提供程序Data Source是地址Initial Catalog是库名ConnectionTimeout单位是秒设短一点能让连接失败更快暴露。Open的四个参数在已经设置ConnectionString时可以传空字符串adConnectUnspecified表示同步打开。Command 的ActiveConnection指向已打开的 Connection这样事务和连接复用都在这条连接上。Execute返回_RecordsetPtr遍历时用adoEOF判断结束字段取值用_variant_t接住再转换。释放顺序是 Recordset、Command、Connection原因在下一节展开。注意连接字符串里的密码不要写进版本库。测试代码可以用环境变量或本地配置文件提交前把Password后面的内容替换掉。2.4 生命周期与释放顺序为什么先关 Recordset 再断 ConnectionADO 对象之间是引用关系Command 持有 Connection 的引用Recordset 又持有 Command 和 Connection 的引用。如果先pConn-Close()再让 Recordset 或 Command 析构常见现象是“对象关闭时不允许操作”或者连接池里留下半开连接。我的习惯是先把 Recordset 关掉并置空再把 Command 的ActiveConnection断开最后关 Connection。这个顺序不玄学就是让引用一层层退掉。另一个边界是 Command 的ActiveConnection可以直接赋连接字符串让 Command 执行时自己打开连接。这样写起来短但事务、连接复用和错误定位都会变麻烦每次执行可能新建连接事务也无法和外部 Connection 对齐。数据访问层里我一般坚持用已打开的 Connection把连接生命周期交给上层Command 只负责命令。多线程场景更要这样ADO 对象不是线程安全的跨线程共用一个_CommandPtr是典型的翻车点后面避坑章节会展开。3. 参数化查询落地CreateParameter 的五个参数与 Execute 的四种返回3.1 为什么 Command 要用 ? 占位符而不是拼接字符串VCAdo 里最常见的错误写法是把用户输入拼进 SQLLSELECT * FROM Users WHERE Name name L。这种写法遇到单引号、日期格式、Unicode 字符就会出问题更不用说注入风险。Command 的参数化查询用?占位符参数值通过Parameters集合传入由 OLE DB 提供程序负责转义和类型转换。SQL 文本里的?按出现顺序绑定不是按名字绑定存储过程才常用Name这种命名参数。把这点分清后面参数顺序错乱的问题会少很多。参数化还有一个实际好处同一条 SQL 模板可以反复执行数据库更容易复用执行计划。批量插入、分页查询、按状态筛选列表这类场景把 Command 对象建一次、参数值改一改再执行比每次拼字符串更稳。需要注意的是不是所有提供程序都支持命名参数SQL 文本里写Name再指望按名匹配在部分驱动上会直接报“参数不足”。所以文本查询统一用?存储过程统一用adCmdStoredProc加Refresh或手动 Append这是比较省心的分界。3.2 CreateParameter 的五个入参Name、Type、Direction、Size、ValueCreateParameter返回一个_ParameterPtr要把它Append到Parameters集合才会生效。五个入参里Name对 SQL 文本参数没有匹配作用但调试时能看到名字Type决定数据库怎么理解这个值Direction决定是输入、输出还是返回值Size对变长类型很关键Value是实际值。下面这张表是我常用的对照具体类型仍要以目标库的列定义为准。入参作用常见取值注意点Name参数名UserId、CitySQL 文本用?时仅作标识Type数据类型adInteger、adVarWChar、adDouble与列类型对齐避免隐式转换Direction参数方向adParamInput、adParamOutput、adParamReturnValue输出参数执行后才能读Size变长类型长度64、128、4000adVarWChar按字符数设置Value参数值_variant_t(18)、_variant_t(LCityA)NULL 用VT_NULL逻辑说明adInteger对应 4 字节整数Size传sizeof(int)只是习惯写法adVarWChar对应宽字符变长字符串Size不要一律写 8000按列定义给 64、128、256 更稳adDouble对应浮点金额场景如果列是money或decimal可以用adCurrency或adDecimal但_variant_t的构造要匹配类型否则会出现隐式转换。Direction在输入参数上写adParamInput输出参数写adParamOutput返回值写adParamReturnValue。参数追加顺序必须和 SQL 里?的顺序一致这点没有捷径。3.3 SELECT 查询绑定参数并取回 _RecordsetPtr下面这段代码查“年龄大于某个值、城市等于某个值”的用户列表。SQL 文本里有两个?所以 Append 两次顺序是年龄、城市。城市值用CityA这种示例值实际替换成业务字段。Execute(NULL, NULL, adCmdText)返回_RecordsetPtr如果查询没有结果Recordset 仍然是打开状态只是adoEOF为真。_CommandPtr pCmd; pCmd.CreateInstance(__uuidof(Command)); pCmd-ActiveConnection pConn; pCmd-CommandType adCmdText; pCmd-CommandText _bstr_t( LSELECT UserId, UserName, Age FROM dbo.Users WHERE Age ? AND City ?); // 第一个 ? 对应 Age第二个 ? 对应 CityAppend 顺序不能反 pCmd-Parameters-Append(pCmd-CreateParameter( _bstr_t(LAge), adInteger, adParamInput, sizeof(int), _variant_t(18))); pCmd-Parameters-Append(pCmd-CreateParameter( _bstr_t(LCity), adVarWChar, adParamInput, 64, _variant_t(LCityA))); _RecordsetPtr pRs pCmd-Execute(NULL, NULL, adCmdText); while (!pRs-adoEOF) { long userId (long)pRs-Fields-Item[_variant_t((long)0)]-Value; _bstr_t userName pRs-Fields-Item[_variant_t((long)1)]-Value; long age (long)pRs-Fields-Item[_variant_t((long)2)]-Value; std::wcout userId L, (wchar_t*)userName L, age std::endl; pRs-MoveNext(); } pRs-Close(); pRs NULL;逻辑说明CommandText里的?是占位符不是字符串里的问号Parameters-Append把创建好的参数加入集合未 Append 的参数不会参与绑定。Fields-Item用序号取值序号从 0 开始和 SELECT 列表顺序一致。_bstr_t userName ...会做一次转换宽字符输出时再转wchar_t*。参数说明adVarWChar的Size64表示最多 64 个宽字符城市名一般够用如果列是nvarchar(200)这里给 200 更稳给太小可能截断或报错。3.4 非查询与影响行数Execute 的 RecordsAffected 怎么读INSERT、UPDATE、DELETE 用同一个 Command 对象执行时返回值可能不是 Recordset而是影响行数。Execute的第一个参数传_variant_t的地址执行后从这个 variant 里读行数。注意不要对非查询的返回值直接解引用 Recordset很多提供程序在这种情况下返回 NULL。行数在某些语句里可能是 -1比如带触发器或某些批处理这时不要拿它做严格业务判断。_CommandPtr pCmd; pCmd.CreateInstance(__uuidof(Command)); pCmd-ActiveConnection pConn; pCmd-CommandType adCmdText; pCmd-CommandText _bstr_t( LUPDATE dbo.Users SET Age ? WHERE UserId ?); pCmd-Parameters-Append(pCmd-CreateParameter( _bstr_t(LAge), adInteger, adParamInput, sizeof(int), _variant_t(30))); pCmd-Parameters-Append(pCmd-CreateParameter( _bstr_t(LUserId), adInteger, adParamInput, sizeof(int), _variant_t(1001))); _variant_t vtRows; _RecordsetPtr pRs pCmd-Execute(vtRows, NULL, adCmdText); long affected (vtRows.vt VT_I4) ? vtRows.lVal : -1; std::wcout Laffected affected std::endl; if (pRs ! NULL pRs-State ! adStateClosed) pRs-Close(); pRs NULL;逻辑说明vtRows接收影响行数pRs可能为 NULL判断vtRows.vt VT_I4再取lVal不要假定一定是 4 字节整数。参数说明adInteger的Value用_variant_t(30)构造类型是VT_I4如果参数值来自界面文本先做_wtoi或更严格的校验不要把字符串直接塞进整数参数。影响行数为 0 不一定是错误可能是条件不匹配为 -1 时看提供程序文档或改用SELECT ROWCOUNT验证。3.5 参数类型与 NULL 值_variant_t 的 VT_NULL 与 adVarWChar 长度NULL 值不能用空字符串代替。字符串参数的 NULL 要构造VT_NULL的_variant_t再赋给参数值。日期参数用adDBTimeStamp或adDate值用_variant_t的VT_DATE金额参数如果列是decimal用adDecimal并设置Precision和NumericScale否则可能出现精度截断。adVarWChar的长度按字符数设置nvarchar(max)用adLongVarWChar不要拿adVarWChar硬塞超长文本。_CommandPtr pCmd; pCmd.CreateInstance(__uuidof(Command)); pCmd-ActiveConnection pConn; pCmd-CommandType adCmdText; pCmd-CommandText _bstr_t( LINSERT INTO dbo.Users(UserName, City, Remark) VALUES(?, ?, ?)); pCmd-Parameters-Append(pCmd-CreateParameter( _bstr_t(LUserName), adVarWChar, adParamInput, 64, _variant_t(LUserA))); pCmd-Parameters-Append(pCmd-CreateParameter( _bstr_t(LCity), adVarWChar, adParamInput, 64, _variant_t(LCityA))); _variant_t vtNull; vtNull.vt VT_NULL; pCmd-Parameters-Append(pCmd-CreateParameter( _bstr_t(LRemark), adLongVarWChar, adParamInput, 4000, vtNull)); _variant_t vtRows; pCmd-Execute(vtRows, NULL, adCmdText);逻辑说明vtNull.vt VT_NULL表示数据库里的 NULL不是空串adLongVar本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
AI模型评估指南:从基础指标到工程实践 1. 从代码到模型:程序员的AI转型之路作为一名转行学习AI大模型的程序员,我深刻理解从传统编程思维转向机器学习评估体系时的困惑与挑战。记得第一次接触模型评估指标时,那些精确率、召回率、F1值就像天书一样令人头疼。但经过半年的实战摸索&… · 2026/11/4 7:30:35
WonderTrader依赖库源码编译与CMake注入避坑 简介:WonderTrader 依赖库面向在 Ubuntu 环境下编译 WonderTrader 量化交易框架的开发者,针对 GCC 11.4.0 与 Boost 等第三方依赖版本不匹配、编译链配置繁琐的问题,提供一套已在该环境验证通过的头文件集合。压缩包共约 2000 个文件… · 2026/11/4 7:30:35
PHP积分商城源码实战:兑换码批量生成与双端部署 简介:这份PHP开源积分商城系统源码面向具备一定PHP基础的开发者与中小型电商项目团队,用于快速搭建积分兑换平台,解决从商品兑换、兑换码发放到后台管理的完整业务闭环问题。压缩包共973个文件,约12.65MB,以385个PHP源… · 2026/11/4 7:29:35
EVIE-4.5B发布:为什么这款4.5B视觉文档检索模型能登顶ViDoRe SOTA榜单? EVIE-4.5B发布:为什么这款4.5B视觉文档检索模型能登顶ViDoRe SOTA榜单? 【免费下载链接】EVIE-4.5B 项目地址: https://ai.gitcode.com/tencent_hunyuan/EVIE-4.5B
EVIE-4.5B 是腾讯开源的一款轻量级视觉文档检索模型(Visual Documen… · 2026/11/4 16:18:02
3行代码接入Android扫码:RxTool把相机、权限与解析都接过去了 3行代码接入Android扫码:RxTool把相机、权限与解析都接过去了 【免费下载链接】RxTool Android开发人员不得不收集的工具类集合 | 支付宝支付 | 微信支付(统一下单) | 微信分享 | Zip4j压缩(支持分卷压缩与加密) | 一键… · 2026/11/4 16:18:02
免费开源WMS:ModernWMS仓库管理系统5分钟快速上手指南 免费开源WMS:ModernWMS仓库管理系统5分钟快速上手指南 【免费下载链接】ModernWMS The open source simple and complete warehouse management system is derived from our many years of experience in implementing erp projects. We stripped the original comm… · 2026/11/4 16:18:02
如何把 7 个字重装进 1 个文件:思源黑体可变字体完整实践指南 如何把 7 个字重装进 1 个文件:思源黑体可变字体完整实践指南 【免费下载链接】source-han-sans Source Han Sans | 思源黑体 | 思源黑體 | 思源黑體 香港 | 源ノ角ゴシック | 본고딕 项目地址: https://gitcode.com/gh_mirrors/so/source-han-sans
一个文件… · 2026/11/4 16:18:02
skill-from-scars:从反复踩过的坑,到可复用的 Agent Skill 使用 AI Coding 工具开发项目时,经常会遇到一种情况:某个错误花了很长时间才解决,相关经验整理到笔记之后,过了一段时间又在另一个项目中遇到类似问题。虽然 AI 能够帮助我们快速修复 Bug,但很多有价值的排查方法并没有… · 2026/11/4 16:17:01
Tiled 地形刷实战:30 分钟让 40×30 地图的过渡瓦片自动铺满 Tiled 地形刷实战:30 分钟让 4030 地图的过渡瓦片自动铺满 【免费下载链接】tiled Flexible level editor 项目地址: https://gitcode.com/gh_mirrors/ti/tiled
做 2D 关卡的开发者,Tiled 替你把"手挑过渡砖"这活接走了。4030 的地图有… · 2026/11/4 16:17:01
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38
AI辅助技术栈评估:提升研发决策效率60% 1. 项目背景与核心价值技术决策一直是研发团队面临的重大挑战。随着技术生态的快速演进,技术栈的选择、维护和迭代变得愈发复杂。我们团队在过去两年里,先后经历了三次重大技术栈迁移,每次都需要投入大量人力进行技术评估和风险分析。直到去年… · 2026/11/4 0:00:17
从生成Token到直接决策:Jev如何重塑AI Agent工程实践 1. 从"生成文字"到"拍板决策":Jev 到底在解决什么问题大多数人第一次接触大模型,脑子里建立的模型是"输入问题、输出文字"。你问它今天天气怎么样,它给你一段描述;你让它写个周报,它给你… · 2026/11/4 0:00:17
32G统一内存跑大模型:内存账、TPS实测与端云分流 想在一台 32G 统一内存的小主机上把大模型跑成"日常可用",这件事我前后折腾了小半年。标题里说的这台 Mac mini M6,是我们这次讨论的目标平台,具体到手规格以实物为准;但下面这套推算方法和实测思路,对任何统… · 2026/11/4 0:00:17
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38