当前位置: 首页 > news >正文

数据库操作丨C++ 操作 数据库——SQLServer 篇

一、背景

C++ Window 操作SQLServer的技术无法以下几种情况:
  • ODBC
  • OLE DB
  • 数据库自己的接口库
  • ADO
  • 第三方库(底层也是ODBC、OLE DB)

1.1 名词解释

ODBC (Open Database Connectivity):这是一种非常标准和通用的数据库访问接口。
OLE DB (Object Linking and Embedding, Database):这是微软提出的一个更现代的、基于 COM (Component Object Model) 的数据访问接口。
ADO (ActiveX Data Objects):ADO 是构建在 OLE DB 之上的一个更高层次的封装。
SQL Server Native Client (SQLNCLI) / Microsoft OLE DB Driver for SQL Server (MSOLEDBSQL) / Microsoft ODBC Driver for SQL Server:这些是专门为 SQL Server 优化的高性能原生驱动库。

1.2 各个技术的现状和简介

技术
诞生背景
核心特点
当前状态
ODBC
统一不同数据库的 C API
C 风格、跨数据库、高性能
依然是主流,有现代驱动支持
OLE DB
统一所有数据源的访问
COM、面向对象、功能强大但复杂
核心技术仍在使用,但仅推荐用于 SQL Server (MSOLEDBSQL)
ADO
简化 OLE DB 的使用
COM、高级封装、简单易用
已过时 (Legacy),不推荐用于新项目
SQLNCLI
快速支持 SQL Server 新特性
将 ODBC/OLE DB 驱动打包
已弃用 (Deprecated),必须迁移

1.3各个技术的演进图

1.4 官方建议

  • 对于新的原生 C++ 项目,应使用现代的 Microsoft ODBC Driver for SQL Server 或 Microsoft OLE DB Driver for SQL Server (MSOLEDBSQL)
  • 对于维护现有的、已经在使用 ADO 的老项目,可以继续使用,但应意识到其功能限制。

二、OLE DB和ODBC 操作数据库

2.1 对比两个数据库操作方法

特性
ODBC
OLE DB
设计目标
SQL 可移植性(跨关系型数据库)
统一数据访问(跨所有数据源)
API 风格
C 语言,过程式,基于句柄 (Handle)
C++/COM,面向对象,基于接口 (Interface)
数据源范围
仅限关系型数据库(或任何提供 ODBC 驱动的数据源)
通用:关系型数据库、文件系统、Excel、邮件等
核心抽象
连接 (Connection)、语句 (Statement)
消费者 (Consumer)、提供程序 (Provider)、会话 (Session)、行集 (Rowset)
平台
跨平台(Windows, Linux, macOS)
主要面向Windows(因其深度依赖 COM)
复杂性
相对简单,学习曲线平缓
非常复杂,需要理解 COM 编程模型
性能
非常高。API 轻量,开销小,接近底层。
可能非常高。原生提供程序 (如 MSOLEDBSQL) 性能极佳。但通过桥接 (如访问 ODBC) 会有额外开销。
当前现状
非常活跃。行业标准,现代驱动持续更新。
基本是遗留技术,除了一个重要例外:用于 SQL Server 的原生驱动 (MSOLEDBSQL)依然被支持和推荐。
备注:
1、在 Windows 上为 SQL Server 的操作上,如果有极致的性能要求,建议选择 OLE DB,其余情况选择的ODBC就可以使用

2.2 ODBC 操作数据

2.2.1 环境配置

ODBC 驱动下载
下载 ODBC Driver for SQL Server - ODBC Driver for SQL Server | Microsoft Learn
ODBC 版本选择问题:注意!!!具体的最好查看一下官方的介绍,数据库连接的加密协议很重要!!!

2.2.2 怎么使用

ODBC 编写流程:
  1. 分配环境句柄: 调用SQLAllocHandle创建一个环境句柄 (SQLHENV)。这是所有操作的顶层容器。
  2. 设置环境属性: 调用SQLSetEnvAttr设置ODBC版本,这是必需的步骤。
  3. 分配连接句柄: 调用SQLAllocHandle创建一个连接句柄 (SQLHDBC)。
  4. 连接数据库: 调用SQLDriverConnect,传入一个连接字符串来建立连接。这比OLE DB的属性集设置要简单得多。
  5. 分配语句句柄: 调用SQLAllocHandle创建一个语句句柄 (SQLHSTMT)。所有SQL执行都与此句柄关联。
  6. 定义数据绑定 (列绑定): 这是ODBC性能的关键。
    1. 定义一个与OLE DB版本相同的C++ struct。
    2. (批量模式)**告诉ODBC我们将一次处理一批数据,通过SQLSetStmtAttr设置SQL_ATTR_ROW_ARRAY_SIZE。
    3. (批量模式)**通过SQLSetStmtAttr设置SQL_ATTR_ROW_BIND_TYPE为我们struct的大小。
    4. 为结果集的每一列调用SQLBindCol,将列直接绑定到struct数组的第一个元素的相应成员上。ODBC会自动处理后续元素的地址偏移。
  7. 执行SQL: 调用SQLExecDirect传入SQL语句。
  8. 抓取并处理数据: 调用SQLFetchScroll来获取下一批数据。由于我们设置了行数组绑定,这一步会自动将整批数据填充到我们的C++ struct数组中。
  9. 手动释放所有句柄: 与分配顺序相反,依次为语句、连接、环境句柄调用SQLFreeHandle。
#include <iostream>
#include <windows.h>
#include <sql.h>       // ODBC 核心头文件
#include <sqlext.h>    // ODBC 扩展头文件
#include <vector>
#include <string>
#include <stdexcept>
#include <iomanip>// 宏,用于检查ODBC函数调用的返回值。如果失败,则打印诊断信息并抛出异常。
void HandleDiagnosticRecord(SQLHANDLE hHandle, SQLSMALLINT hType, RETCODE RetCode);
#define CHECK_ODBC(ret, hHandle, hType) { if (!SQL_SUCCEEDED(ret)) { HandleDiagnosticRecord(hHandle, hType, ret); throw std::runtime_error("ODBC call failed. Check console for details."); } }// 1. 定义一个与OLE DB版本完全相同的C++结构体来接收数据
struct ProductDataODBC {SQLINTEGER productID;SQLWCHAR   name[51];    // nvarchar(50) -> WCHAR[51]double     listPrice;   // money -> double// ODBC绑定也需要为每个可变长度或可为NULL的字段提供一个长度/状态指示器SQLLEN     name_len_or_ind;SQLLEN     listPrice_len_or_ind;
};void RunOdbcDemo() {std::wcout << L"\n\n--- Starting ODBC DEMO ---\n" << std::endl;// --- ODBC 句柄:所有ODBC操作都围绕这些句柄进行 ---SQLHENV hEnv = NULL;SQLHDBC hDbc = NULL;SQLHSTMT hStmt = NULL;RETCODE ret; // 用于接收ODBC函数调用的返回码try {// --- 流程 1 & 2: 分配并设置环境句柄 ---ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv); CHECK_ODBC(ret, hEnv, SQL_HANDLE_ENV);// 必须设置ODBC版本为3.xret = SQLSetEnvAttr(hEnv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0); CHECK_ODBC(ret, hEnv, SQL_HANDLE_ENV);std::wcout << L"1. ODBC Environment Handle created and configured." << std::endl;// --- 流程 3 & 4: 分配连接句柄并连接到数据库 ---ret = SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDbc); CHECK_ODBC(ret, hDbc, SQL_HANDLE_DBC);// 使用连接字符串,这比OLE DB的属性集更直观SQLWCHAR connStrIn[] = L"DRIVER={ODBC Driver 17 for SQL Server};SERVER=YourServerName;DATABASE=AdventureWorks2019;UID=YourUsername;PWD=YourPassword;"; // !!! 替换为你的信息ret = SQLDriverConnectW(hDbc, NULL, connStrIn, SQL_NTS, NULL, 0, NULL, SQL_DRIVER_NOPROMPT);CHECK_ODBC(ret, hDbc, SQL_HANDLE_DBC);std::wcout << L"2. Connection successful via ODBC Driver." << std::endl;// --- 流程 5: 分配语句句柄 ---ret = SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt); CHECK_ODBC(ret, hStmt, SQL_HANDLE_STMT);std::wcout << L"3. Statement Handle created." << std::endl;// --- 流程 6: 数据绑定 (在执行前进行批量绑定) ---// 这是ODBC高性能的关键:将结果集列直接绑定到一个C++结构体数组const int BATCH_SIZE = 10; // 我们要一次性获取10行std::vector<ProductDataODBC> productBatch(BATCH_SIZE);// 告诉ODBC我们将使用行优先的绑定,并一次处理BATCH_SIZE行ret = SQLSetStmtAttr(hStmt, SQL_ATTR_ROW_BIND_TYPE, (SQLPOINTER)sizeof(ProductDataODBC), 0); CHECK_ODBC(ret, hStmt, SQL_HANDLE_STMT);ret = SQLSetStmtAttr(hStmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)BATCH_SIZE, 0); CHECK_ODBC(ret, hStmt, SQL_HANDLE_STMT);// 告诉ODBC将获取到的行数存入这个变量SQLULEN numRowsFetched = 0;ret = SQLSetStmtAttr(hStmt, SQL_ATTR_ROWS_FETCHED_PTR, &numRowsFetched, 0); CHECK_ODBC(ret, hStmt, SQL_HANDLE_STMT);// 将结果集列绑定到结构体数组的第一个元素的对应成员// ODBC会自动处理后续元素的内存偏移ret = SQLBindCol(hStmt, 1, SQL_C_LONG,  &productBatch[0].productID, sizeof(SQLINTEGER), &productBatch[0].name_len_or_ind); CHECK_ODBC(ret, hStmt, SQL_HANDLE_STMT);ret = SQLBindCol(hStmt, 2, SQL_C_WCHAR, &productBatch[0].name, sizeof(productBatch[0].name), &productBatch[0].name_len_or_ind); CHECK_ODBC(ret, hStmt, SQL_HANDLE_STMT);ret = SQLBindCol(hStmt, 3, SQL_C_DOUBLE, &productBatch[0].listPrice, sizeof(double), &productBatch[0].listPrice_len_or_ind); CHECK_ODBC(ret, hStmt, SQL_HANDLE_STMT);std::wcout << L"4. Column binding for batch fetching completed." << std::endl;// --- 流程 7: 执行SQL ---SQLWCHAR* wcsSQL = (SQLWCHAR*)L"SELECT TOP 10 ProductID, Name, ListPrice FROM Production.Product ORDER BY ProductID";ret = SQLExecDirectW(hStmt, wcsSQL, SQL_NTS); CHECK_ODBC(ret, hStmt, SQL_HANDLE_STMT);std::wcout << L"5. SQL command executed." << std::endl;// --- 流程 8: 批量抓取数据 ---// 关键的一步:一次调用就将所有10行数据填充到我们的vector中ret = SQLFetchScroll(hStmt, SQL_FETCH_NEXT, 0);if (ret == SQL_NO_DATA) {std::wcout << L"No data found." << std::endl;} else {CHECK_ODBC(ret, hStmt, SQL_HANDLE_STMT);std::wcout << L"6. Batch fetch successful. " << numRowsFetched << L" rows obtained in one call." << std::endl;// --- 处理已直接写入到vector中的数据 ---std::wcout << L"\n--- Fetched Data (ODBC) ---" << std::endl;for (ULONG i = 0; i < numRowsFetched; ++i) {std::wcout << L"  ID: " << std::setw(4) << productBatch[i].productID<< L", Name: " << std::left << std::setw(30) << (productBatch[i].name_len_or_ind == SQL_NULL_DATA ? L"NULL" : productBatch[i].name) << std::right<< L", Price: " << std::fixed << std::setprecision(2) << (productBatch[i].listPrice_len_or_ind == SQL_NULL_DATA ? -1.0 : productBatch[i].listPrice)<< std::endl;}}}catch (const std::exception& ex) {std::cerr << "An exception occurred in ODBC demo: " << ex.what() << std::endl;}// --- 流程 9: 清理所有句柄 (顺序与分配相反) ---std::wcout << L"\n--- Cleaning up ODBC resources ---" << std::endl;if (hStmt) SQLFreeHandle(SQL_HANDLE_STMT, hStmt);if (hDbc) {SQLDisconnect(hDbc);SQLFreeHandle(SQL_HANDLE_DBC, hDbc);}if (hEnv) SQLFreeHandle(SQL_HANDLE_ENV, hEnv);std::wcout << L"All ODBC resources released." << std::endl;
}// ODBC错误处理函数的实现
void HandleDiagnosticRecord(SQLHANDLE hHandle, SQLSMALLINT hType, RETCODE RetCode) {SQLSMALLINT i = 0;SQLINTEGER  native;SQLWCHAR    state[7];SQLWCHAR    text[256];SQLSMALLINT len;while (SQLGetDiagRec(hType, hHandle, ++i, state, &native, text, sizeof(text)/sizeof(SQLWCHAR), &len) == SQL_SUCCESS) {std::wcerr << L"[ODBC Error] STATE: " << state << L", NATIVE: " << native << L", MSG: " << text << std::endl;}
}// 主函数入口,可以用来依次调用OLE DB和ODBC的DEMO
int main() {// 您可以将之前的OLE DB代码封装成一个 RunOledbDemo() 函数// RunOledbDemo(); RunOdbcDemo();return 0;
}

2.2.3 完整代码

#include "OdbcWrapper.h"
//==============================================================================
// OdbcWrapper Class Implementation
//==============================================================================OdbcWrapper::OdbcWrapper() :m_hEnv(SQL_NULL_HANDLE),m_hDbc(SQL_NULL_HANDLE),m_isConnected(false)
{// 构造函数中分配环境句柄并设置ODBC版本if (SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_hEnv))) {if (!SQL_SUCCEEDED(SQLSetEnvAttr(m_hEnv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0))) {SetError(SQL_HANDLE_ENV, m_hEnv, L"Failed to set ODBC version.");SafeFreeHandle(m_hEnv, SQL_HANDLE_ENV);}}else {m_lastError = L"Failed to allocate environment handle.";}
}OdbcWrapper::~OdbcWrapper() {Disconnect();SafeFreeHandle(m_hEnv, SQL_HANDLE_ENV); // 确保环境句柄在最后被释放
}void OdbcWrapper::Disconnect() {if (m_hDbc != SQL_NULL_HANDLE) {if (m_isConnected) {SQLDisconnect(m_hDbc);}SafeFreeHandle(m_hDbc, SQL_HANDLE_DBC);}m_isConnected = false;
}bool OdbcWrapper::Connect(const std::wstring& connectionString) {if (m_hEnv == SQL_NULL_HANDLE) return false;Disconnect(); // 先断开旧连接m_lastError.clear();SQLRETURN ret;// 分配连接句柄ret = SQLAllocHandle(SQL_HANDLE_DBC, m_hEnv, &m_hDbc);if (!SQL_SUCCEEDED(ret)) {SetError(SQL_HANDLE_ENV, m_hEnv, L"Failed to allocate connection handle.");return false;}// 使用连接字符串进行连接std::vector<wchar_t> connStrBuf(connectionString.begin(), connectionString.end());connStrBuf.push_back(L'\0');ret = SQLDriverConnect(m_hDbc, NULL, connStrBuf.data(), SQL_NTS, NULL, 0, NULL, SQL_DRIVER_NOPROMPT);if (!SQL_SUCCEEDED(ret)) {SetError(SQL_HANDLE_DBC, m_hDbc, L"SQLDriverConnect failed.");Disconnect(); // 失败后清理return false;}m_isConnected = true;return true;
}bool OdbcWrapper::ExecuteQuery(const std::wstring& sql, std::vector<std::map<std::wstring, std::wstring>>& results) {results.clear();m_lastError.clear();if (!m_isConnected) {m_lastError = L"Not connected to a database.";return false;}SQLHSTMT hStmt = SQL_NULL_HANDLE; // 语句句柄是局部的SQLRETURN ret;// --- 步骤 1: 分配语句句柄 ---ret = SQLAllocHandle(SQL_HANDLE_STMT, m_hDbc, &hStmt);if (!SQL_SUCCEEDED(ret)) {SetError(SQL_HANDLE_DBC, m_hDbc, L"Failed to allocate statement handle.");return false;}// --- 步骤 2: 执行 SQL ---// SQLExecDirect可以直接执行SQL语句ret = SQLExecDirect(hStmt, (SQLWCHAR*)sql.c_str(), SQL_NTS);if (!SQL_SUCCEEDED(ret)) {SetError(SQL_HANDLE_STMT, hStmt, L"SQLExecDirect failed.");SafeFreeHandle(hStmt, SQL_HANDLE_STMT);return false;}// --- 步骤 3: 获取列信息 ---SQLSMALLINT nCols = 0;ret = SQLNumResultCols(hStmt, &nCols);if (!SQL_SUCCEEDED(ret) || nCols <= 0) {// 这不是一个错误,可能是一个没有返回结果的 DML 语句SafeFreeHandle(hStmt, SQL_HANDLE_STMT);return true;}std::vector<std::wstring> colNames(nCols);std::vector<std::vector<wchar_t>> colData(nCols);std::vector<SQLLEN> colLen(nCols);for (SQLSMALLINT i = 0; i < nCols; ++i) {wchar_t colNameBuf[256];SQLSMALLINT nameLen;SQLSMALLINT dataType;SQLULEN colSize;SQLSMALLINT decimalDigits;SQLSMALLINT nullable;SQLDescribeCol(hStmt, i + 1, colNameBuf, sizeof(colNameBuf) / sizeof(wchar_t),&nameLen, &dataType, &colSize, &decimalDigits, &nullable);colNames[i] = colNameBuf;// 为数据绑定分配缓冲区,加1用于空终止符colData[i].resize(4097); // 分配一个足够大的缓冲区 (例如4KB)SQLBindCol(hStmt, i + 1, SQL_C_WCHAR, colData[i].data(), colData[i].size() * sizeof(wchar_t), &colLen[i]);}// --- 步骤 4: 循环获取行数据 ---while (SQL_SUCCEEDED(SQLFetch(hStmt))) {std::map<std::wstring, std::wstring> row;for (SQLSMALLINT i = 0; i < nCols; ++i) {if (colLen[i] == SQL_NULL_DATA) {row[colNames[i]] = L"(NULL)";}else {row[colNames[i]] = colData[i].data();}}results.push_back(row);}// --- 步骤 5: 清理 ---SafeFreeHandle(hStmt, SQL_HANDLE_STMT);return true;
}std::wstring OdbcWrapper::GetLastError() const {return m_lastError;
}void OdbcWrapper::SetError(SQLSMALLINT handleType, SQLHANDLE handle, const std::wstring& message) {wchar_t sqlState[6];SQLINTEGER nativeError;wchar_t msg[SQL_MAX_MESSAGE_LENGTH];SQLSMALLINT msgLen;std::wstring errorDetails;SQLSMALLINT i = 1;while (SQL_SUCCEEDED(SQLGetDiagRec(handleType, handle, i, sqlState, &nativeError, msg, sizeof(msg) / sizeof(wchar_t), &msgLen))) {wchar_t buffer[2048];swprintf_s(buffer, L"\n  - SQLSTATE: %s, Native Error: %d, Message: %s", sqlState, nativeError, msg);errorDetails += buffer;i++;}m_lastError = message + errorDetails;
}

2.3 OLE DB操作数据

2.3.1 环境配置

OLE DB驱动下载:
下载适用于 SQL Server 的 Microsoft OLE DB 驱动程序 - OLE DB Driver for SQL Server | Microsoft Learn
OLE DB 版本选择问题:注意!!!具体的最好查看一下官方的介绍,数据库连接的加密协议很重要!!!
  • COM 环境: 初始化 (CoInitialize)。
  • 创建数据源对象: 使用CoCreateInstance创建驱动实例 (e.g., CLSID_MSOLEDBSQL),获取IDBInitialize接口。
  • 设置连接属性: 创建一个DBPROPSET数组,用DBPROP结构体填充服务器、数据库、用户名、密码等信息。
  • 连接: 调用IDBInitialize::Initialize建立连接。
  • 创建会话与命令: 从连接对象获取IDBCreateSession,然后创建IDBCreateCommand。
  • 设置并执行SQL: 从命令对象获取ICommandText接口,设置SQL语句,然后调用ICommand::Execute,返回一个行集接口IRowset。
  • 定义数据绑定 (创建访问器): 这是最复杂的部分。a. 定义一个C++ struct来映射一行数据。b. 创建一个DBBINDING结构体数组,将结果集的每一列精确地映射到C++ struct的成员变量的内存地址、类型和长度。c. 从行集对象获取IAccessor接口,调用CreateAccessor方法,用绑定信息创建一个高效的访问器句柄HACCESSOR。
  • 抓取并处理数据: 循环调用IRowset::GetNextRows获取行句柄HROW,然后调用IRowset::GetData并传入访问器句柄,数据会被直接写入你之前绑定的C++ struct中。
  • 手动释放所有资源: 必须为每一个获取到的COM接口指针手动调用Release()方法,并为访问器句柄调用ReleaseAccessor。

2.3.2 数据库的连接

流图
关键函数说明​:
  1. ​**CoInitialize(nullptr)**​
  • 初始化 COM 库,OLE DB 依赖此环境 。
  1. ​**CoCreateInstance(CLSID_MSOLEDBSQL, ...)**​
  • 创建 OLE DB 数据源对象(IDBInitialize),优先选择现代驱动 。
  1. ​**IDBProperties::SetProperties(rgPropSets)**​
  • 配置连接属性(服务器地址、数据库名、账号密码等)。
  1. ​**IDBInitialize::Initialize()**​
  • 触发实际数据库连接和身份验证,失败返回 0x80040E4D 需重点检查。这个错误可能是版本问题,以及 guidPropertySet属性问题
配置属性说明
属性名​
​数据类型​
​所属属性集​
​作用​
​推荐值​
​必填​
DBPROP_INIT_DATASOURCE
VT_BSTR
DBPROPSET_DBINIT
服务器地址(IP 或主机名)
127.0.0.1
DBPROP_INIT_CATALOG
VT_BSTR
DBPROPSET_DBINIT
目标数据库名
master
DBPROP_AUTH_INTEGRATED
VT_BSTR
DBPROPSET_DBINIT
Windows 身份验证(值固定为 "SSPI")
SSPI
可选
DBPROP_AUTH_USERID
VT_BSTR
DBPROPSET_DBINIT
SQL 登录账号
sa
可选
DBPROP_AUTH_PASSWORD
VT_BSTR
DBPROPSET_DBINIT
SQL 登录密码
your_password
可选
SSPROP_INIT_ENCRYPT
VT_BOOL
DBPROPSET_SQLSERVERDBINIT
启用 TLS 加密(生产环境必备)
VARIANT_TRUE
建议启用
SSPROP_INIT_TRUST_SERVER_CERTIFICATE
VT_BOOL
DBPROPSET_SQLSERVERDBINIT
信任自签名证书(测试环境适用)
VARIANT_TRUE(测试)
可选
DBPROP_MULTIPLECONNECTIONS
VT_BOOL
DBPROPSET_DATASOURCE
是否允许多连接并行操作(默认单连接阻塞)
VARIANT_FALSE
DBPROP_CURRENTCATALOG
VT_BSTR
DBPROPSET_DATASOURCE
获取或设置当前数据库(等效 USE database)
动态设置
#ifdef OLE_DB_19hr = CoCreateInstance(CLSID_MSOLEDBSQL19,        // Microsoft OLE DB Driver for SQL Servernullptr,CLSCTX_INPROC_SERVER,IID_IDBInitialize,       // 直接请求IDBInitialize接口(void**)&m_pIDBInitialize);
#elsehr = CoCreateInstance(CLSID_MSOLEDBSQL,        // Microsoft OLE DB Driver for SQL Servernullptr,CLSCTX_INPROC_SERVER,IID_IDBInitialize,       // 直接请求IDBInitialize接口(void**)&m_pIDBInitialize);
#endif // #ifdef OLE_DB_19if (FAILED(hr)) {//异常处理}std::vector<DBPROP> rgProps;DBPROP prop;///  addPropBool addProp 的匿名函数代码.... addProp(DBPROP_INIT_DATASOURCE, server);addProp(DBPROP_INIT_CATALOG, database);if (useTrustedConnection) {addProp(DBPROP_AUTH_INTEGRATED, L"SSPI");}else {addProp(DBPROP_AUTH_USERID, user);addProp(DBPROP_AUTH_PASSWORD, password);}// 禁用加密addPropBool(SSPROP_INIT_ENCRYPT, VARIANT_FALSE);// 或者使用信任服务器证书(如果启用加密)addPropBool(SSPROP_INIT_TRUST_SERVER_CERTIFICATE, VARIANT_TRUE);// 4. 设置连接属性IDBProperties* pIDBProperties = nullptr;hr = m_pIDBInitialize->QueryInterface(IID_IDBProperties, (void**)&pIDBProperties);if (FAILED(hr)) {//异常处理}DBPROPSET rgPropSets[1] = {};rgPropSets[0].guidPropertySet = /*DBPROPSET_SQLSERVERDBINIT*/DBPROPSET_DBINIT;rgPropSets[0].cProperties = (ULONG)rgProps.size();rgPropSets[0].rgProperties = &rgProps[0];hr = pIDBProperties->SetProperties(1, rgPropSets);for (auto& p : rgProps) {VariantClear(&p.vValue);}if (FAILED(hr)) {//异常处理}hr = pIDBProperties->QueryInterface(IID_IDBInitialize, (void**)&m_pIDBInitialize);SafeRelease(pIDBProperties);if (FAILED(hr)) {//异常处理}hr = m_pIDBInitialize->Initialize();if (FAILED(hr)) {//异常处理}

2.3.3 数据库的查询

键函数说明​:
  1. ​**IDBCreateSession::CreateSession(IID_IUnknown, &pSession)**​
  • 创建会话对象,用于后续命令操作 。
  1. ​**IDBCreateCommand::CreateCommand(IID_ICommandText, &pICommandText)**​
  • 生成命令对象,支持 SQL 语句执行 。
  1. ​**ICommandText::SetCommandText(DBGUID_DBSQL, query)**​
  • 绑定 SQL 查询文本(如 SELECT * FROM sys.databases)。
  1. ​**ICommandText::Execute(..., IID_IRowset, &pRowset)**​
  • 执行查询并返回结果集句柄 IRowset 。
  1. ​**IColumnsInfo::GetColumnInfo(&numColumns, &pColumnInfo)**​
  • 获取列元数据(名称、类型、长度),用于动态数据绑定 。
  1. ​**IAccessor::CreateAccessor(DBBINDING[...], &hAccessor)**​
  • 创建内存访问器,定义列数据解析规则(当前代码硬编码为 DBTYPE_STR,需优化)。
  1. ​**IRowset::GetNextRows(NULL, 0, 1, &cRowsObtained, &pRows)**​
  • 逐行获取数据,单次仅取 1 行(建议改为批量获取提升性能)。
  1. ​**IRowset::GetData(hRow, hAccessor, pData)**​
  • 按绑定规则解析行数据,需配合 MultiByteToWideChar 转换编码
    // 1. 创建会话hr = m_pIDBInitialize->QueryInterface(IID_IDBCreateSession, (void**)&pIDBCreateSession);if (FAILED(hr)) {// 异常操作}// 2. 创建会话对象IUnknown* pSession = nullptr;hr = pIDBCreateSession->CreateSession(nullptr, IID_IUnknown, &pSession);if (FAILED(hr)) {// 异常操作}// 3. 创建命令对象IDBCreateCommand* pIDBCreateCommand = nullptr;hr = pSession->QueryInterface(IID_IDBCreateCommand, (void**)&pIDBCreateCommand);SafeRelease(pSession);if (FAILED(hr)) {// 异常操作}// 4. 创建命令文本对象ICommandText* pICommandText = nullptr;hr = pIDBCreateCommand->CreateCommand(nullptr, IID_ICommandText, (IUnknown**)&pICommandText);SafeRelease(pIDBCreateCommand);if (FAILED(hr)) {// 异常操作}if (SUCCEEDED(hr = pICommandText->SetCommandText(DBGUID_DBSQL, sql.c_str()))) {hr = pICommandText->Execute(nullptr, IID_IRowset, nullptr, nullptr, (IUnknown**)&pIRowset);}if (FAILED(hr)) {// 异常操作}//其他代码if (SUCCEEDED(pIRowset->QueryInterface(IID_IColumnsInfo, (void**)&pIColumnsInfo))) {if (SUCCEEDED(pIColumnsInfo->GetColumnInfo(&nCols, &pColInfo, &pStringsBuffer))) {for (DBORDINAL i = 0; i < nCols; ++i) {colNames.push_back(pColInfo[i].pwszName ? pColInfo[i].pwszName : L"");}}}
// 以下就是获取所有数据的代码

2.3.4 完整DEMO代码

#include <iostream>
#include <windows.h>
#include <comdef.h>
#include <io.h>
#include <fcntl.h>
#include <vector>#include <oledb.h>
#include <msoledbsql.h>#define SafeRelease(p) { if(p) { (p)->Release(); (p) = nullptr; } }// 打印详细错误信息
void PrintDetailedError(const wchar_t* message, HRESULT hr) {_com_error err(hr);std::wcerr << L"--- ERROR ---" << std::endl;std::wcerr << L"Location: " << message << std::endl;std::wcerr << L"HRESULT: 0x" << std::hex << hr << L" (" << err.ErrorMessage() << L")" << std::endl;IErrorInfo* pErrorInfo = nullptr;if (SUCCEEDED(GetErrorInfo(0, &pErrorInfo)) && pErrorInfo != nullptr) {BSTR bstrDesc = nullptr;pErrorInfo->GetDescription(&bstrDesc);if (bstrDesc) {std::wcerr << L"OLE DB Description: " << bstrDesc << std::endl;SysFreeString(bstrDesc);}SafeRelease(pErrorInfo);}// 尝试获取OLE DB特定错误信息IErrorRecords* pErrorRecords = nullptr;if (SUCCEEDED(GetErrorInfo(0, IID_IErrorRecords, (IUnknown**)&pErrorRecords) && pErrorRecords) {ULONG cRecords = 0;pErrorRecords->GetRecordCount(&cRecords);for (ULONG i = 0; i < cRecords; i++) {ERRORINFO errorInfo;IErrorInfo* pRecordErrorInfo = nullptr;if (SUCCEEDED(pErrorRecords->GetBasicErrorInfo(i, &errorInfo)) {std::wcerr << L"OLE DB Error: " << errorInfo.dwMinor << L" - " << errorInfo.iid << std::endl;}if (SUCCEEDED(pErrorRecords->GetErrorInfo(i, 0, &pRecordErrorInfo)) && pRecordErrorInfo) {BSTR bstrDesc = nullptr;pRecordErrorInfo->GetDescription(&bstrDesc);if (bstrDesc) {std::wcerr << L"Error Details: " << bstrDesc << std::endl;SysFreeString(bstrDesc);}SafeRelease(pRecordErrorInfo);}}SafeRelease(pErrorRecords);}std::wcerr << L"-------------" << std::endl;
}// 执行SQL查询并显示结果
HRESULT ExecuteSQLQuery(IUnknown* pDataSource, const wchar_t* query) {HRESULT hr;// 1. 创建会话IDBCreateSession* pIDBCreateSession = nullptr;hr = pDataSource->QueryInterface(IID_IDBCreateSession, (void**)&pIDBCreateSession);if (FAILED(hr)) {PrintDetailedError(L"QueryInterface for IDBCreateSession", hr);return hr;}std::wcout << L"Step 7: IDBCreateSession obtained." << std::endl;// 2. 创建会话对象IUnknown* pSession = nullptr;hr = pIDBCreateSession->CreateSession(nullptr, IID_IUnknown, &pSession);SafeRelease(pIDBCreateSession);if (FAILED(hr)) {PrintDetailedError(L"CreateSession", hr);return hr;}std::wcout << L"Step 8: Session created." << std::endl;// 3. 创建命令对象IDBCreateCommand* pIDBCreateCommand = nullptr;hr = pSession->QueryInterface(IID_IDBCreateCommand, (void**)&pIDBCreateCommand);SafeRelease(pSession);if (FAILED(hr)) {PrintDetailedError(L"QueryInterface for IDBCreateCommand", hr);return hr;}std::wcout << L"Step 9: IDBCreateCommand obtained." << std::endl;// 4. 创建命令文本对象ICommandText* pICommandText = nullptr;hr = pIDBCreateCommand->CreateCommand(nullptr, IID_ICommandText, (IUnknown**)&pICommandText);SafeRelease(pIDBCreateCommand);if (FAILED(hr)) {PrintDetailedError(L"CreateCommand", hr);return hr;}std::wcout << L"Step 10: Command object created." << std::endl;// 5. 设置SQL命令文本hr = pICommandText->SetCommandText(DBGUID_DBSQL, query);if (FAILED(hr)) {PrintDetailedError(L"SetCommandText", hr);SafeRelease(pICommandText);return hr;}std::wcout << L"Step 11: Query set: \"" << query << L"\"" << std::endl;// 6. 执行查询IRowset* pRowset = nullptr;hr = pICommandText->Execute(nullptr, IID_IRowset, nullptr, nullptr, (IUnknown**)&pRowset);SafeRelease(pICommandText);if (FAILED(hr)) {PrintDetailedError(L"Execute query", hr);return hr;}std::wcout << L"Step 12: Query executed successfully." << std::endl;// 7. 获取列信息DBCOLUMNINFO* pColumnInfo = nullptr;IColumnsInfo* pColumnsInfo = nullptr;OLECHAR* pColumnNames = nullptr;ULONG numColumns = 0;hr = pRowset->QueryInterface(IID_IColumnsInfo, (void**)&pColumnsInfo);if (SUCCEEDED(hr)) {hr = pColumnsInfo->GetColumnInfo(&numColumns, &pColumnInfo, &pColumnNames);if (FAILED(hr)) {numColumns = 0;}}// 8. 显示列名if (numColumns > 0) {std::wcout << L"\nQuery Results:\n";std::wcout << L"----------------------------------------\n";std::wcout << L"| ";for (ULONG i = 0; i < numColumns; i++) {std::wcout << pColumnInfo[i].pwszName << L" | ";}std::wcout << L"\n----------------------------------------\n";}// 9. 获取行数据HACCESSOR hAccessor = NULL;IAccessor* pAccessor = nullptr;hr = pRowset->QueryInterface(IID_IAccessor, (void**)&pAccessor);if (SUCCEEDED(hr) && numColumns > 0) {DBBINDING* pBindings = new DBBINDING[numColumns];DBLENGTH rowSize = 0;// 为每列创建绑定for (ULONG i = 0; i < numColumns; i++) {pBindings[i].iOrdinal = i + 1;pBindings[i].obValue = rowSize;pBindings[i].obLength = rowSize + sizeof(DBSTATUS);pBindings[i].obStatus = rowSize + sizeof(DBSTATUS) + sizeof(ULONG);pBindings[i].pTypeInfo = nullptr;pBindings[i].pObject = nullptr;pBindings[i].pBindExt = nullptr;pBindings[i].dwPart = DBPART_VALUE | DBPART_LENGTH | DBPART_STATUS;pBindings[i].dwMemOwner = DBMEMOWNER_CLIENTOWNED;pBindings[i].eParamIO = DBPARAMIO_NOTPARAM;pBindings[i].cbMaxLen = 255; // 最大长度pBindings[i].dwFlags = 0;pBindings[i].wType = DBTYPE_STR;pBindings[i].bPrecision = pColumnInfo[i].bPrecision;pBindings[i].bScale = pColumnInfo[i].bScale;rowSize += sizeof(DBSTATUS) + sizeof(ULONG) + pBindings[i].cbMaxLen;rowSize = (rowSize + 7) & ~7; // 8字节对齐}// 创建访问器hr = pAccessor->CreateAccessor(DBACCESSOR_ROWDATA, numColumns, pBindings, rowSize, &hAccessor, nullptr);if (SUCCEEDED(hr)) {BYTE* pData = new BYTE[rowSize];HROW hRow = NULL;HROW* pRows = &hRow;// 获取行句柄while (SUCCEEDED(pRowset->GetNextRows(NULL, 0, 1, &cRowsObtained, &pRows)) && cRowsObtained > 0) {// 获取行数据hr = pRowset->GetData(hRow, hAccessor, pData);if (SUCCEEDED(hr)) {std::wcout << L"| ";for (ULONG i = 0; i < numColumns; i++) {DBBINDING* pBind = &pBindings[i];DBSTATUS status = *(DBSTATUS*)(pData + pBind->obStatus);if (status == DBSTATUS_S_OK) {char* pValue = (char*)(pData + pBind->obValue);ULONG len = *(ULONG*)(pData + pBind->obLength);// 转换为宽字符串输出wchar_t wcValue[256] = {0};MultiByteToWideChar(CP_ACP, 0, pValue, len, wcValue, 255);std::wcout << wcValue << L" | ";} else {std::wcout << L"NULL | ";}}std::wcout << std::endl;}pRowset->ReleaseRows(1, pRows, nullptr, nullptr, nullptr);}delete[] pData;pAccessor->ReleaseAccessor(hAccessor, nullptr);}delete[] pBindings;}// 清理资源if (pColumnInfo) CoTaskMemFree(pColumnInfo);if (pColumnNames) CoTaskMemFree(pColumnNames);SafeRelease(pColumnsInfo);SafeRelease(pAccessor);SafeRelease(pRowset);std::wcout << L"\nQuery completed." << std::endl;return S_OK;
}int main() {// 设置控制台输出为Unicode模式_setmode(_fileno(stdout), _O_U16TEXT);_setmode(_fileno(stderr), _O_U16TEXT);HRESULT hr;// 1. 初始化COM环境hr = CoInitialize(nullptr);if (FAILED(hr)) {std::wcerr << L"CoInitialize failed." << std::endl;return -1;}std::wcout << L"Step 1: CoInitialize OK." << std::endl;// 数据库连接参数const wchar_t* server = L"127.0.0.1";const wchar_t* database = L"master";const wchar_t* user = L"sa";const wchar_t* password = L"your_password";const wchar_t* query = L"SELECT name, database_id, create_date FROM sys.databases";// 2. 准备连接属性std::vector<DBPROP> rgProps;DBPROP prop;// Lambda函数:添加字符串类型的属性auto addPropString = [&](DWORD dwPropertyID, const std::wstring& value) {VariantInit(&prop.vValue);prop.dwPropertyID = dwPropertyID;prop.dwOptions = DBPROPOPTIONS_REQUIRED;prop.colid = DB_NULLID;prop.vValue.vt = VT_BSTR;prop.vValue.bstrVal = SysAllocString(value.c_str());rgProps.push_back(prop);};// Lambda函数:添加布尔类型的属性auto addPropBool = [&](DWORD dwPropertyID, VARIANT_BOOL value) {VariantInit(&prop.vValue);prop.dwPropertyID = dwPropertyID;prop.dwOptions = DBPROPOPTIONS_REQUIRED;prop.colid = DB_NULLID;prop.vValue.vt = VT_BOOL;prop.vValue.boolVal = value;rgProps.push_back(prop);};// 添加核心连接属性addPropString(DBPROP_INIT_DATASOURCE, server);       // 服务器地址addPropString(DBPROP_INIT_CATALOG, database);        // 数据库名称addPropString(DBPROP_AUTH_USERID, user);             // 用户名addPropString(DBPROP_AUTH_PASSWORD, password);       // 密码// 添加加密选项addPropBool(SSPROP_INIT_ENCRYPT, VARIANT_FALSE);     // 禁用加密// 配置属性集DBPROPSET rgPropSets[1] = {};rgPropSets[0].guidPropertySet = DBPROPSET_DBINIT;    // 使用标准属性集rgPropSets[0].cProperties = (ULONG)rgProps.size();rgPropSets[0].rgProperties = &rgProps[0];std::wcout << L"Step 2: Properties prepared." << std::endl;// 3. 创建OLE DB数据源对象IDBInitialize* pIDBInitialize = nullptr;hr = CoCreateInstance(CLSID_MSOLEDBSQL,        // Microsoft OLE DB Driver for SQL Servernullptr,CLSCTX_INPROC_SERVER,IID_IDBInitialize,       // 直接请求IDBInitialize接口(void**)&pIDBInitialize);if (FAILED(hr)) {PrintDetailedError(L"CoCreateInstance", hr);std::wcout << L"Trying legacy SQLOLEDB driver..." << std::endl;return -1;} else {std::wcout << L"Using MSOLEDBSQL driver" << std::endl;}// 4. 设置连接属性IDBProperties* pIDBProperties = nullptr;hr = pIDBInitialize->QueryInterface(IID_IDBProperties, (void**)&pIDBProperties);if (SUCCEEDED(hr)) {hr = pIDBProperties->SetProperties(1, rgPropSets);SafeRelease(pIDBProperties);}// 清理属性内存for (auto& p : rgProps) VariantClear(&p.vValue);if (FAILED(hr)) {PrintDetailedError(L"SetProperties", hr);SafeRelease(pIDBInitialize);CoUninitialize();return -1;}std::wcout << L"Step 3: SetProperties OK." << std::endl;// 5. 初始化连接std::wcout << L"Step 4: Initializing connection..." << std::endl;hr = pIDBInitialize->Initialize();if (FAILED(hr)) {PrintDetailedError(L"Initialize", hr);SafeRelease(pIDBInitialize);CoUninitialize();return -1;}std::wcout << L"Step 5: CONNECTION SUCCEEDED!" << std::endl;// 6. 执行SQL查询std::wcout << L"\nStep 6: Executing SQL query..." << std::endl;HRESULT queryResult = ExecuteSQLQuery(pIDBInitialize, query);if (FAILED(queryResult)) {std::wcerr << L"\nQuery execution failed. See error details above." << std::endl;}// 7. 清理资源if (pIDBInitialize) {pIDBInitialize->Uninitialize();SafeRelease(pIDBInitialize);}CoUninitialize();return 0;
}

2.4 测试结论

8337 行数据 ODBC 21108 ms OLE DB 20734 ms
耗时时间基本都是一个比较稳定,差异不是特别大,如果选择的优化 还是建议优先选择ODBC,OLE DB的使用更复杂度和陷阱更多。
ODBC代码 AI一搜基本都能获取到正确的代码,而OLE DB AI写的不一定正确,注意!!!,所以ODBC 写的比较简洁

三、ADO(新项目不推荐用) 历史遗留项目

就不写DEMO了,这个底层就是OLE DB和ODBC,意味着ADO可以切换不同底层,故要注意底层选择的驱动,
可以使用 UDL文件验证有哪些可以使用的OLE DB
http://www.dtcms.com/a/289474.html

相关文章:

  • 数据库技术总结
  • 激光雷达和相机在线标定
  • 试用SAP BTP 06:AI服务-Data Attribute Recommendation
  • Java行为型模式---解释器模式
  • 30天打牢数模基础-XgBoost讲解
  • 第四章第一节 OLED 调试工具
  • 【LeetCode 热题 100】200. 岛屿数量——DFS
  • 20250720-3-Kubernetes 调度-资源限制对Pod调度的影响(2)_笔记
  • 隧道无线调频广播与“群载波”全频插播技术在张石高速黑石岭隧道中的应用
  • 数据结构第二章:线性表之顺序表
  • Kubernetes (K8S)知识详解
  • 【k8s集群管理平台】k8s运维管理的新玩法,让运维电脑随时不离身的现状成为过去
  • 【论文研读】SlowFast Networks for Video Recognition
  • 2024年全国青少年信息素养大赛Scratch算法创意实践挑战赛 小高组 初赛 真题
  • http基础一
  • HarmonyOS 启动提速秘籍:懒加载全链路实战解析
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘lxml’问题
  • 红宝书单词学习笔记 list 51-75
  • 基于Chinese-LLaMA-Alpaca-3的多模态中医舌诊辅助诊断系统设计与实现
  • securecrt连接服务器报错 Key exchange failed 怎么办
  • QFutureInterface和QFuture间联系与区别
  • 力扣 hot100 Day50
  • Transformers基础组件—Model(上)
  • shared_ptr创建方式以及循环引用问题
  • MES系列 - MES是提升制造执行效率与透明度的关键系统
  • 单线程 Reactor 模式
  • C++ 继承和多态
  • linux安装Mysql后添加mysql的用户和密码
  • 负的 Content-Length 问题解析与修复方案
  • Claude Code 逆向工程分析,探索最新Agent设计