数据库操作丨C++ 操作 数据库——SQLServer 篇
1、在 Windows 上为 SQL Server 的操作上,如果有极致的性能要求,建议选择 OLE DB,其余情况选择的ODBC就可以使用。耗时时间基本都是一个比较稳定,差异不是特别大,如果选择的优化 还是建议优先选择ODBC,OLE DB的使用更复杂度和陷阱更多。ODBC代码 AI一搜基本都能获取到正确的代码,而OLE DB AI写的不一定正确,注意!,所以ODBC 写的比较简洁。消费者 (C
·
一、背景
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 版本选择问题:注意!!!具体的最好查看一下官方的介绍,数据库连接的加密协议很重要!!!
2.2.2 怎么使用
ODBC 编写流程:
- 分配环境句柄: 调用SQLAllocHandle创建一个环境句柄 (SQLHENV)。这是所有操作的顶层容器。
- 设置环境属性: 调用SQLSetEnvAttr设置ODBC版本,这是必需的步骤。
- 分配连接句柄: 调用SQLAllocHandle创建一个连接句柄 (SQLHDBC)。
- 连接数据库: 调用SQLDriverConnect,传入一个连接字符串来建立连接。这比OLE DB的属性集设置要简单得多。
- 分配语句句柄: 调用SQLAllocHandle创建一个语句句柄 (SQLHSTMT)。所有SQL执行都与此句柄关联。
- 定义数据绑定 (列绑定): 这是ODBC性能的关键。
- 定义一个与OLE DB版本相同的C++ struct。
- (批量模式)**告诉ODBC我们将一次处理一批数据,通过SQLSetStmtAttr设置SQL_ATTR_ROW_ARRAY_SIZE。
- (批量模式)**通过SQLSetStmtAttr设置SQL_ATTR_ROW_BIND_TYPE为我们struct的大小。
- 为结果集的每一列调用SQLBindCol,将列直接绑定到struct数组的第一个元素的相应成员上。ODBC会自动处理后续元素的地址偏移。
- 执行SQL: 调用SQLExecDirect传入SQL语句。
- 抓取并处理数据: 调用SQLFetchScroll来获取下一批数据。由于我们设置了行数组绑定,这一步会自动将整批数据填充到我们的C++ struct数组中。
- 手动释放所有句柄: 与分配顺序相反,依次为语句、连接、环境句柄调用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.x
ret = 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驱动下载:
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 数据库的连接
流图
关键函数说明:
- **CoInitialize(nullptr)**
- 初始化 COM 库,OLE DB 依赖此环境 。
- **CoCreateInstance(CLSID_MSOLEDBSQL, ...)**
- 创建 OLE DB 数据源对象(IDBInitialize),优先选择现代驱动 。
- **IDBProperties::SetProperties(rgPropSets)**
- 配置连接属性(服务器地址、数据库名、账号密码等)。
- **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_19
hr = CoCreateInstance(
CLSID_MSOLEDBSQL19, // Microsoft OLE DB Driver for SQL Server
nullptr,
CLSCTX_INPROC_SERVER,
IID_IDBInitialize, // 直接请求IDBInitialize接口
(void**)&m_pIDBInitialize
);
#else
hr = CoCreateInstance(
CLSID_MSOLEDBSQL, // Microsoft OLE DB Driver for SQL Server
nullptr,
CLSCTX_INPROC_SERVER,
IID_IDBInitialize, // 直接请求IDBInitialize接口
(void**)&m_pIDBInitialize
);
#endif // #ifdef OLE_DB_19
if (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 数据库的查询
键函数说明:
- **IDBCreateSession::CreateSession(IID_IUnknown, &pSession)**
- 创建会话对象,用于后续命令操作 。
- **IDBCreateCommand::CreateCommand(IID_ICommandText, &pICommandText)**
- 生成命令对象,支持 SQL 语句执行 。
- **ICommandText::SetCommandText(DBGUID_DBSQL, query)**
- 绑定 SQL 查询文本(如 SELECT * FROM sys.databases)。
- **ICommandText::Execute(..., IID_IRowset, &pRowset)**
- 执行查询并返回结果集句柄 IRowset 。
- **IColumnsInfo::GetColumnInfo(&numColumns, &pColumnInfo)**
- 获取列元数据(名称、类型、长度),用于动态数据绑定 。
- **IAccessor::CreateAccessor(DBBINDING[...], &hAccessor)**
- 创建内存访问器,定义列数据解析规则(当前代码硬编码为 DBTYPE_STR,需优化)。
- **IRowset::GetNextRows(NULL, 0, 1, &cRowsObtained, &pRows)**
- 逐行获取数据,单次仅取 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 Server
nullptr,
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可以切换不同底层,故要注意底层选择的驱动,
// 构建 OLE DB 连接字符串
std::wstring oledbConnStr = L"Provider=MSOLEDBSQL19;" // 指定使用 MSOLEDBSQL19 提供程序
+ L"Server=" + server + L";"
+ L"Database=" + database + L";"
+ L"UID=" + uid + L";"
+ L"PWD=" + pwd + L";"
+ L"Encrypt=no;"; // 明确禁用加密
// 构建一个内嵌的 ODBC 连接字符串
std::wstring odbcDriver = L"ODBC Driver 18 for SQL Server";
std::wstring embeddedOdbcConnStr = L"Driver={" + odbcDriver + L"};"
+ L"Server=" + server + L";"
+ L"Database=" + database + L";"
+ L"UID=" + uid + L";"
+ L"PWD=" + pwd + L";"
+ L"Encrypt=no;";
OLE DB参数说明
| 键 (Key) | 值 (Value) | 含义与解释 |
| Provider | MSOLEDBSQL19 | 这是最重要的参数。它告诉 ADO:“请加载并使用名为 MSOLEDBSQL19 的 OLE DB 提供程序”。MSOLEDBSQL19 是 "Microsoft OLE DB Driver 19 for SQL Server" 的程序化标识符 (ProgID)。ADO 会根据这个名字在 Windows 注册表中查找并实例化对应的 COM 对象。 |
| Server | IP地址/服务器名称 | 指定目标 SQL Server 的网络地址。可以是 IP 地址、主机名,或者是 主机名\实例名 的格式 (例如 MYPC\SQLEXPRESS)。 |
| Database | DBName | 指定成功连接到服务器后,要将哪个数据库作为当前的工作数据库。后续不带数据库名的查询都会在此数据库上执行。这个参数也常被称为 Initial Catalog。 |
| UID | 账户 | User ID (用户标识)。指定用于登录的 SQL Server 用户名。这表明我们正在使用“SQL Server 身份验证”模式。 |
| PWD | 密码 | Password (密码)。与 UID 配合使用的密码。 |
| Encrypt | no | 这是一个 SQL Server 特定参数。它指示 OLE DB 提供程序在与服务器通信时如何处理加密。no (或 false) 明确地告诉驱动不要尝试建立加密连接。这对于连接到未配置有效证书的开发服务器非常有用。其他值还包括 yes (强制加密) 和 Optional (默认值,驱动会尝试加密)。 |
ODBC参数说明
| 键 (Key) | 值 (Value) | 含义与解释 |
| Driver | {ODBC Driver 18 for SQL Server} | 这是内嵌 ODBC 字符串的核心。它告诉 ODBC 驱动管理器:“请加载名为 ODBC Driver 18 for SQL Server 的 ODBC 驱动程序”。注意花括号 {} 是 ODBC 规范中推荐的写法。 |
| Server | IP地址/服务器名称 | 指定目标 SQL Server 的网络地址。可以是 IP 地址、主机名,或者是 主机名\实例名 的格式 (例如 MYPC\SQLEXPRESS)。 |
| Database | DBName | 指定成功连接到服务器后,要将哪个数据库作为当前的工作数据库。后续不带数据库名的查询都会在此数据库上执行。这个参数也常被称为 Initial Catalog。 |
| UID | 账户 | User ID (用户标识)。指定用于登录的 SQL Server 用户名。这表明我们正在使用“SQL Server 身份验证”模式。 |
| PWD | 密码 | Password (密码)。与 UID 配合使用的密码。 |
| Encrypt | no | 这是由 ODBC Driver 18 for SQL Server 这个ODBC 驱动所支持的特定参数。 |
可以使用 UDL文件验证有哪些可以使用的OLE DB
四、小结
新的项目建议使用ODBC,通用且一致被官方维护。OLE DB操作比较复杂,可以考虑使用ADO代替。但是ADO新项目不是很推荐使用。
有且仅有在追求极限性能,且环境是Window SQLServer的前提下,才建议使用OLE DB。
最后 博主遇到最大坑,就是驱动和版本有关联,不要盲目用最新版本驱动。除非你的数据库也是最新的版本。
更多推荐
所有评论(0)