1. 引言

ADO (ActiveX Data Objects) 是微软提供的一组COM组件,用于在C++、VB等语言中高效访问数据库。在VC++(特别是MFC)项目中,ADO因其接口简洁、功能强大而被广泛使用。本文将以一篇经典的技术博客为基础,系统地梳理在VC++中使用ADO进行数据库编程的核心步骤与最佳实践,并辅以架构图和流程图,帮助开发者快速掌握。

2. 核心架构与流程概览

2.1 ADO编程核心架构图

下图清晰地展示了VC++ ADO编程中涉及的三个核心智能指针对象及其协作关系。

VC++ Application

执行SQL

执行SQL/存储过程

打开/操作

应用程序

_ConnectionPtr

_CommandPtr

_RecordsetPtr

数据库连接
Connection

数据库
e.g., Access, SQL Server

结果集/影响行数

架构解读

  • _ConnectionPtr: 负责建立和维护与数据库的物理连接,是所有数据操作的基础。
  • _CommandPtr: 用于执行不返回结果集的SQL命令(如INSERT, UPDATE, DELETE)或调用存储过程。
  • _RecordsetPtr: 用于处理从数据库返回的结果集(SELECT查询),支持对记录的遍历、增删改查。
2.2 典型ADO数据库操作流程图

遵循以下流程,可以安全、高效地完成一次数据库会话。

成功

执行SQL

操作结果集

失败

开始

准备工作

引入ADO库
#import msado15.dll

初始化COM库
AfxOleInit或CoInitialize

连接数据库

创建_ConnectionPtr实例

调用Open方法
提供连接字符串

连接成功

选择操作类型

_CommandPtr或
_ConnectionPtr::Execute

_RecordsetPtr Open

处理执行结果

遍历/增删改记录集

关闭连接/记录集

释放COM库
CoUninitialize
MFC中自动管理

结束

异常捕获与处理

3. 模块化详解

3.1 模块一:环境准备与数据库连接

此模块是ADO所有操作的基石。

  • 核心代码(MFC环境)
    // 1. 引入ADO类型库
    #import "C:\\Program Files\\Common Files\\System\\ado\\msado15.dll" \
        no_namespace \
        rename("EOF", "adoEOF") // 避免与标准EOF冲突
    
    // 2. 初始化COM库 (在应用初始化处调用,如CWinApp::InitInstance)
    if (!AfxOleInit()) {
        AfxMessageBox(_T("初始化OLE失败!"));
        return FALSE;
    }
    
    // 3. 创建连接并打开数据库
    _ConnectionPtr m_pConnection = NULL;
    HRESULT hr = m_pConnection.CreateInstance(__uuidof(Connection));
    if (SUCCEEDED(hr)) {
        // 设置连接超时
        m_pConnection->ConnectionTimeout = 5;
        // 连接字符串示例:连接Access数据库
        CString strConn = _T("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\path\\to\\your\\db.mdb;");
        hr = m_pConnection->Open((_bstr_t)strConn, _T(""), _T(""), adModeUnknown);
        if (FAILED(hr)) {
            _com_error e(hr);
            AfxMessageBox(e.ErrorMessage());
        }
    }
    
3.2 模块二:记录集操作

这是与数据交互最频繁的部分。

  • 操作流程图

记录集操作流程

创建_RecordsetPtr

Open打开记录集

操作类型

遍历读取

添加记录

修改记录

删除记录

MoveNext/adoEOF判断

AddNew -> PutCollect -> Update

直接PutCollect -> Update

Delete -> Update

关闭记录集

  • 核心代码示例(遍历与添加)
    // 打开记录集
    _RecordsetPtr m_pRecordset = NULL;
    m_pRecordset.CreateInstance(__uuidof(Recordset));
    m_pRecordset->Open(_T("SELECT * FROM Users"), // SQL
                      m_pConnection.GetInterfacePtr(), // 活动连接
                      adOpenDynamic,
                      adLockOptimistic,
                      adCmdText);
    
    // 遍历读取
    while (!m_pRecordset->adoEOF) {
        _variant_t varName = m_pRecordset->GetCollect(_T("UserName"));
        _variant_t varAge = m_pRecordset->GetCollect(_T("Age"));
        if (varName.vt != VT_NULL && varAge.vt != VT_NULL) {
            CString strName = (LPCTSTR)(_bstr_t)varName;
            int nAge = (int)varAge;
            // 处理数据...
        }
        m_pRecordset->MoveNext();
    }
    
    // 添加新记录
    m_pRecordset->AddNew();
    m_pRecordset->PutCollect(_T("UserName"), _variant_t(_T("JohnDoe")));
    m_pRecordset->PutCollect(_T("Age"), _variant_t(30L));
    m_pRecordset->Update();
    
3.3 模块三:执行SQL与存储过程

对于不返回结果集或需要高效执行的操作。

  • 方法对比表
方法 适用场景 优点 核心代码片段
_ConnectionPtr::Execute 快速执行不返回结果集的SQL(UPDATE, INSERT, DELETE) 接口最简洁,一行代码完成 m_pConnection->Execute(_T("UPDATE T SET F=1"), NULL, adCmdText);
_CommandPtr 1. 执行参数化SQL
2. 调用存储过程
3. 需要重用SQL命令
功能强大,支持参数,性能更优 m_pCommand->CommandText = _T("EXEC MyProc");
m_pRecordset = m_pCommand->Execute(NULL,NULL,adCmdStoredProc);
  • 调用存储过程示例
    _CommandPtr m_pCmd;
    m_pCmd.CreateInstance(__uuidof(Command));
    m_pCmd->ActiveConnection = m_pConnection; // 绑定连接
    m_pCmd->CommandText = _T("sp_GetEmployeeByDept"); // 存储过程名
    m_pCmd->CommandType = adCmdStoredProc;
    
    // 添加参数(示例)
    _ParameterPtr pParam = m_pCmd->CreateParameter(_T("@DeptID"), adInteger, adParamInput, 4, _variant_t(10L));
    m_pCmd->Parameters->Append(pParam);
    
    _RecordsetPtr rs = m_pCmd->Execute(NULL, NULL, adCmdStoredProc);
    
3.4 模块四:数据库元数据探索

用于动态获取数据库结构信息。

  • 流程图:遍历数据库表与字段

开始

建立数据库连接

使用OpenSchema
adSchemaTables

获取表记录集

记录集结束?

读取TABLE_NAME和TABLE_TYPE

过滤TABLE_TYPE == 'TABLE'

输出或处理表名

MoveNext

对目标表,获取其字段

使用Fields集合
get_Count与get_Item

遍历所有字段
获取Name等属性

结束

4. 关键数据类型转换速查表

在ADO编程中,_variant_t_bstr_t是处理数据的关键桥梁。

转换方向 代码示例 说明
CString -> _variant_t _variant_t varVal(strCString); 用于将字符串赋值给记录集字段。
_variant_t -> CString CString str = (LPCTSTR)(_bstr_t)varVal; 推荐方式,先转为_bstr_t再转为CString
long/int -> _variant_t _variant_t varVal(100L); 用于赋值整型字段。
_variant_t -> long long lVal = (long)varVal; 直接强制转换,需确保vt类型正确。
CString -> BSTR BSTR bstr = strCString.AllocSysString();
... SysFreeString(bstr); // 务必释放
需手动管理内存。
BSTR -> CString CString strCString = (LPCTSTR)CW2T(bstr); 使用ATL宏安全转换。
CString -> _bstr_t _bstr_t bstrT = (LPCTSTR)strCString; _bstr_t自动管理内存,更安全。
_bstr_t -> CString CString strCString = (LPCTSTR)bstrT; 直接转换。

5. 总结与最佳实践建议

  1. 错误处理:务必使用try...catch(_com_error e)包围所有ADO调用,并使用e.ErrorMessage()获取错误信息。
  2. 资源释放:操作完成后,按顺序关闭RecordsetConnection,并将指针置NULL。在非MFC程序中,需配对调用CoInitializeCoUninitialize
  3. 连接字符串:对于不同数据库(如SQL Server),需修改连接字符串的ProviderData Source等参数。
  4. 智能指针:充分利用_ConnectionPtr_RecordsetPtr_CommandPtr这三个智能指针,它们能自动管理COM引用计数。
  5. 效率考量:频繁操作时,考虑使用_CommandPtr进行参数化查询以减少SQL解析开销,并合理设置CommandTimeoutConnectionTimeout

更多推荐