ADO (ActiveX Data Objects) 是 COM 組件。VC++ 使用 `#import` 導入類型庫生成智能指針 (`_ConnectionPtr`, `_RecordsetPtr` 等),自動管理 `AddRef/Release`,極大簡化 COM 開發。
// stdafx.h 或公共頭文件
#import "C:\\Program Files\\Common Files\\System\\ADO\\msado15.dll" \
no_namespace rename("EOF", "adoEOF") // 避免 EOF 宏衝突
// 常用智能指針類型
// _ConnectionPtr, _CommandPtr, _RecordsetPtr, _ParameterPtr, ErrorsPtr, ErrorPtr
關鍵屬性:`no_namespace` 省去 `ADODB::` 前綴;`rename` 解決宏衝突。
HRESULT InitCOM() {
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); // STA 模式
// 或 CoInitialize(NULL) (舊版)
return hr;
}
void UninitCOM() {
CoUninitialize();
}
// RAII 封裝 (推薦)
class ComInitializer {
public:
ComInitializer() : hr_(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)) {}
~ComInitializer() { if (SUCCEEDED(hr_)) CoUninitialize(); }
operator HRESULT() const { return hr_; }
private:
HRESULT hr_;
};
// 使用
void DatabaseOperation() {
ComInitializer com;
if (FAILED(com)) return;
// ... ADO 操作
} // 離開作用域自動 CoUninitialize
_ConnectionPtr CreateConnection(LPCTSTR connStr) {
_ConnectionPtr pConn(__uuidof(Connection)); // 智能指針創建實例
pConn->ConnectionTimeout = 20; // 秒
pConn->Open(_bstr_t(connStr), _bstr_t(L""), _bstr_t(L""), adConnectUnspecified);
return pConn; // 智能指針複製 (AddRef),離開作用域自動 Release
}
// 連接字串範例
// Access: "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\Data\\db.mdb;"
// SQL Server: "Provider=SQLOLEDB;Server=.;Database=Test;UID=sa;PWD=123;"
// SQL Server (ODBC): "Driver={SQL Server};Server=.;Database=Test;UID=sa;PWD=123;"
_RecordsetPtr ExecuteQuery(_ConnectionPtr pConn, LPCTSTR sql) {
_variant_t rowsAffected;
_RecordsetPtr pRs = pConn->Execute(_bstr_t(sql), &rowsAffected, adCmdText);
return pRs; // 只支持前向只讀、唯讀游標
}
_RecordsetPtr ExecuteParamQuery(_ConnectionPtr pConn, LPCTSTR sql, const std::vector<_variant_t>& params) {
_CommandPtr pCmd(__uuidof(Command));
pCmd->ActiveConnection = pConn;
pCmd->CommandText = _bstr_t(sql);
pCmd->CommandType = adCmdText; // 或 adCmdStoredProc
// 添加參數 (順序必須與 SQL 中 ? 一致)
for (size_t i = 0; i < params.size(); ++i) {
_ParameterPtr pParam = pCmd->CreateParameter(
_bstr_t(L""), adVariant, adParamInput, 0, params[i]);
pCmd->Parameters->Append(pParam);
}
_RecordsetPtr pRs = pCmd->Execute(NULL, NULL, adCmdText);
return pRs;
}
// 使用
std::vector<_variant_t> params = { _variant_t(L"admin"), _variant_t(1) };
_RecordsetPtr pRs = ExecuteParamQuery(pConn, L"SELECT * FROM Users WHERE Name=? AND Status=?", params);
void ProcessRecordset(_RecordsetPtr pRs) {
if (!pRs || pRs->adoEOF) return;
// 獲取列信息 (可選)
long colCount = pRs->Fields->Count;
for (long i = 0; i < colCount; ++i) {
_bstr_t colName = pRs->Fields->GetItem((long)i)->Name;
DataTypeEnum colType = pRs->Fields->GetItem((long)i)->Type;
// 打印列名/類型...
}
// 遍歷行
while (!pRs->adoEOF) {
for (long i = 0; i < colCount; ++i) {
_variant_t val = pRs->Fields->GetItem(i)->Value;
if (val.vt == VT_NULL) {
// NULL 值處理
} else {
// 類型安全提取
switch (val.vt) {
case VT_I4: int n = val.lVal; break;
case VT_BSTR: CString s = (LPCTSTR)_bstr_t(val); break;
case VT_DATE: COleDateTime dt = val.date; break;
case VT_CY: CURRENCY cy = val.cyVal; break;
// ... 其他類型
}
}
}
pRs->MoveNext();
}
}
void SafeAdoOperation() {
ComInitializer com;
if (FAILED(com)) return;
try {
_ConnectionPtr pConn(__uuidof(Connection));
pConn->Open(...);
try {
_RecordsetPtr pRs = pConn->Execute(L"SELECT * FROM Table");
// 處理...
}
catch (_com_error& e) {
// 1. COM 層錯誤 (HRESULT, IErrorInfo)
CString msg;
msg.Format(_T("COM Error: 0x%08lx - %s\nSource: %s\nDescription: %s"),
e.Error(), e.ErrorMessage(), (LPCTSTR)e.Source(), (LPCTSTR)e.Description());
AfxMessageBox(msg);
}
// 2. ADO 提供者錯誤 (Connection.Errors 集合)
if (pConn->Errors->Count > 0) {
for (long i = 0; i < pConn->Errors->Count; ++i) {
ErrorPtr pErr = pConn->Errors->GetItem(i);
CString adoErr;
adoErr.Format(_T("ADO Error: 0x%lx - %s"), pErr->Number, (LPCTSTR)pErr->Description);
TRACE(adoErr);
}
}
}
catch (...) {
AfxMessageBox(_T("未知嚴重錯誤"));
}
}
void TransactionSample(_ConnectionPtr pConn) {
pConn->BeginTrans();
try {
pConn->Execute(L"INSERT INTO T1 VALUES (1)");
pConn->Execute(L"UPDATE T2 SET C=1 WHERE ID=2");
pConn->CommitTrans();
}
catch (_com_error&) {
pConn->RollbackTrans();
throw;
}
}
| 問題 | 解決 |
|---|---|
| 編譯錯誤:`EOF` 重定義 | `rename("EOF", "adoEOF")` 或 `#undef EOF` |
| 連接字串 Provider 錯誤 | Access 用 `Microsoft.Jet.OLEDB.4.0` (32位) / `Microsoft.ACE.OLEDB.12.0` (64位/新版) |
| 參數順序錯誤 | OLE DB 使用位置參數 `?`,Append 順序必須與 SQL 中 `?` 順序完全一致 |
| 記錄集游標類型限制 | `Execute` 返回 `adOpenForwardOnly`/`adLockReadOnly`;需可更新游標請用 `_CommandPtr` 設置 `CursorType`/`LockType` 後 `Open` |
| 大數據量效能 | 使用 `adUseClient` 客戶端游標 (`CursorLocation`),分頁獲取 (`PageSize`/`AbsolutePage`) |
| 64 位編譯找不到 Provider | 安裝 64 位 Access Database Engine / SQL Server Native Client |
VC++ 調用 ADO 核心在於:`#import` 智能指針管理 COM 生命週期、`_variant_t`/`_bstr_t` 封裝 VARIANT/BSTR、`_com_error` 捕獲 COM 異常、`Connection.Errors` 診斷提供者錯誤。**參數化命令 (`_CommandPtr`) 是防注入、支援存儲過程、類型安全的標準做法**。新專案建議遷移至 ODBC/現代數據訪問庫,降低 COM 複雜度與部署依賴。