在 Visual C++ 專案中操作資料庫,ADO (ActiveX Data Objects) 是最成熟、相容性最佳的選擇。本文詳細說明如何在 MFC 專案中正確引入 ADO、建立連線、操作 Recordset,並針對常見坑點(COM 初始化、智慧指標、資源釋放、資料類型對應)提供最佳實踐代碼範本。
stdafx.h) 加入:#include <afxwin.h> // MFC 核心
#include <afxext.h> // MFC 擴充
#include <afxdisp.h> // MFC 自動化 (IDispatch)
#include <afxdao.h> // DAO (可選)
#include "ado.h" // 自訂 ADO 智慧指標包裝
ado.h) — 使用 #import 產生型別安全的包裝類別:#ifndef __ADO_H__
#define __ADO_H__
#import "msado15.dll" no_namespace rename("EOF", "adoEOF") rename("BOF", "adoBOF")
// 常用智慧指標 typedef
typedef _ConnectionPtr ADOConnectionPtr;
typedef _RecordsetPtr ADORecordsetPtr;
typedef _CommandPtr ADOCommandPtr;
typedef _ParameterPtr ADOParameterPtr;
typedef FieldPtr ADOFieldPtr;
typedef FieldsPtr ADOFieldsPtr;
#endif
rename 避免 EOF/BOF 與 MFC 巨集衝突;no_namespace 簡化程式碼(視專案習慣決定)。
::CoInitialize(NULL); // 或 CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)
ADOConnectionPtr pConn;
ADORecordsetPtr pRs;
HRESULT hr = pConn.CreateInstance(__uuidof(Connection));
hr = pRs.CreateInstance(__uuidof(Recordset));
_bstr_t connStr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\Data\\Demo.mdb;";
pConn->Open(connStr, "", "", adConnectUnspecified);
// 開啟 Recordset
pRs->Open(_bstr_t("SELECT * FROM Products"),
_variant_t((IDispatch*)pConn),
adOpenKeyset, adLockOptimistic, adCmdText);
// 綁定至 MS DataGrid ActiveX 控件
m_datagrid.ClearFields();
m_datagrid.SetRefDataSource(pRs); // 需引用 MSDATAGRIDLib
// 1. 解除控件綁定
m_datagrid.SetRefDataSource(NULL);
// 2. 關閉 Recordset
if (pRs && pRs->State == adStateOpen) {
pRs->Close();
}
pRs = NULL; // 智慧指標自動 Release()
// 3. 關閉 Connection
if (pConn && pConn->State == adStateOpen) {
pConn->Close();
}
pConn = NULL;
// 4. 釋放 COM
::CoUninitialize();
ADOCommandPtr pCmd;
pCmd.CreateInstance(__uuidof(Command));
pCmd->ActiveConnection = pConn;
pCmd->CommandText = "SELECT * FROM Users WHERE UserID = ? AND Status = ?";
pCmd->CommandType = adCmdText;
// 附加參數 (順序必須與 SQL 佔位符一致)
pCmd->Parameters->Append(pCmd->CreateParameter("UserID", adInteger, adParamInput, 4, 1001));
pCmd->Parameters->Append(pCmd->CreateParameter("Status", adVarChar, adParamInput, 20, "Active"));
ADORecordsetPtr pRs2;
pRs2 = pCmd->Execute(NULL, NULL, adCmdText);
| ADO DataTypeEnum | C++/MFC 類型 | 取值範例 |
|---|---|---|
| adSmallInt / adInteger | long / int | pField->Value.lVal |
| adDouble / adNumeric | double | pField->Value.dblVal |
| adDate | DATE / COleDateTime | pField->Value.date |
| adCurrency | CURRENCY / COleCurrency | pField->Value.cyVal |
| adVarChar / adBSTR | CString / _bstr_t | (LPCTSTR)(_bstr_t)pField->Value |
| adBoolean | VARIANT_BOOL / bool | pField->Value.boolVal == VARIANT_TRUE |
_variant_t val = pRs->GetCollect("FieldName"); // 或 pRs->Fields->GetItem("FieldName")->Value
if (val.vt == VT_NULL || val.vt == VT_EMPTY) {
// 處理 NULL
} else {
switch (val.vt) {
case VT_I4: int n = val.lVal; break;
case VT_R8: double d = val.dblVal; break;
case VT_BSTR: CString s = (LPCTSTR)(_bstr_t)val; break;
case VT_DATE: COleDateTime dt = val.date; break;
// ... 其他類型
}
}
CreateInstance 返回 REGDB_E_CLASSNOTREG 或 CO_E_NOTINITIALIZED。Microsoft.ACE.OLEDB.12.0,並安裝 Access Database Engine。adOpenForwardOnly 不支援 adLockOptimistic,需改 adOpenKeyset 或 adOpenStatic。_ConnectionPtr 與 _RecordsetPtr 互相引用,改用 WeakPtr 模式或手動 Release()。_com_error 拋出,建議統一用 try-catch(_com_error& e) 捕獲並記錄 e.ErrorMessage()。class AdoSession {
public:
AdoSession() { ::CoInitialize(NULL); }
~AdoSession() { ::CoUninitialize(); }
bool OpenDB(LPCTSTR connStr) {
m_pConn.CreateInstance(__uuidof(Connection));
return SUCCEEDED(m_pConn->Open(_bstr_t(connStr), "", "", adConnectUnspecified));
}
ADORecordsetPtr Query(LPCTSTR sql) {
ADORecordsetPtr rs;
rs.CreateInstance(__uuidof(Recordset));
rs->Open(_bstr_t(sql), _variant_t((IDispatch*)m_pConn),
adOpenStatic, adLockReadOnly, adCmdText);
return rs; // 智慧指標自動管理生命週期
}
void Close() {
if (m_pConn && m_pConn->State == adStateOpen) m_pConn->Close();
m_pConn = NULL;
}
private:
ADOConnectionPtr m_pConn;
};
使用方式:
AdoSession session;
if (session.OpenDB(_T("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=App.accdb;"))) {
ADORecordsetPtr rs = session.Query(_T("SELECT * FROM Users"));
while (!rs->adoEOF) {
CString name = (LPCTSTR)(_bstr_t)rs->Fields->GetItem("Name")->Value;
// 處理資料...
rs->MoveNext();
}
}
session.Close();