CRecordset::forwardOnly 游標無法取得欄位數GetFieldValue() 例外來反推欄位數 —— 絕非良策作者自製 CMYODBC + CODBCSet,代碼精簡 (<2000 行),核心優勢:**查詢結果動態綁定**,無需預知 Schema。
SQLAllocHandle(SQL_HANDLE_ENV) → 環境初始化SQLAllocHandle(SQL_HANDLE_DBC) → 連線控制代碼SQLConnect / SQLDriverConnect → 連線資料源SQLAllocHandle(SQL_HANDLE_STMT) → 陳述式控制代碼SQLExecDirect / SQLPrepare+SQLExecute → 執行 SQLSQLNumResultCols → 取得欄位數 (關鍵!)SQLColAttribute / SQLDescribeCol → 取得欄位名、型別、長度SQLBindCol → 綁定緩衝區至欄位SQLFetch → 逐列擷取資料SQLSetConnectAttr(SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF) → SQLEndTran(SQL_COMMIT/ROLLBACK)SQLFreeHandle (STMT/DBC/ENV)class CMYODBC {
public:
BOOL ConnectDB(const char* cpServerName, const char* cpUserName, const char* cpPassword);
BOOL ExeSqlDirect(const char* cpSqlStmt); // INSERT/UPDATE/DELETE
BOOL ExecTrans(CStringList& strSqlList); // 事務批次執行
BOOL PrepareSql(const char* cpSql, CODBCSet& rset); // SELECT 預備 (動態綁定)
BOOL FetchData(); // 擷取下一列
void DisConnect();
// ... 內部封裝 HENV, HDBC, HSTMT 等控制代碼
};
struct COL_DATAFMT_ODBC { // 欄位中繼資料
char name[50]; // 欄位名
SWORD type; // SQL 資料型別
UDWORD len; // 定義長度
SWORD scale; // 小數位數
// ...
};
struct COL_DATA_ODBC { // 單列資料緩衝區
char value[256]; // 字串化後的值
SDWORD len; // 實際長度
};
class CODBCSet {
public:
int GetCols() const; // 欄位數 (SQLNumResultCols 結果)
COL_DATAFMT_ODBC m_coldatafmt[100]; // 最大 100 欄位
COL_DATA_ODBC m_coldata[100]; // 當前列資料
void Empty(); // 清理緩衝區
// ... 內部由 CMYODBC::PrepareSql 填充
};
BOOL CDemo1Dlg::ShowData() {
m_list.DeleteAllItems();
// 清除舊欄位 (需分兩次迴圈,合併會失敗)
for (int i=0; i<m_list.GetHeaderCtrl()->GetItemCount(); i++)
m_list.DeleteColumn(0);
CString strSql = _T("select * from emp");
CMyODBC db; CODBCSet set;
db.ConnectDB("daliu", "", ""); // Access 無帳密
db.PrepareSql(strSql, set); // 關鍵:動態綁定發生在此
// 動態建立欄位
for (int i=0; i<set.GetCols(); i++)
m_list.InsertColumn(i, set.m_coldatafmt[i].name, LVCFMT_CENTER, 80);
// 擷取資料
int iRow = 0;
while (db.FetchData()) {
for (int i=0; i<set.GetCols(); i++) {
if (i==0) m_list.InsertItem(iRow, set.m_coldata[0].value);
else m_list.SetItemText(iRow, i, set.m_coldata[i].value);
}
iRow++;
}
set.Empty(); db.DisConnect();
return TRUE;
}
void CDemo1Dlg::OnBtnadd() {
UpdateData(TRUE);
CString strSql;
strSql.Format(_T("insert into emp values(%d,'%s','%s')"),
_ttoi(m_strID), m_strName, m_strJob);
CMyODBC db;
db.ConnectDB("daliu", "", "");
db.ExeSqlDirect(strSql);
db.DisConnect();
ShowData(); // 重新整理
}
CStringList sqlList;
sqlList.AddTail(_T("insert into emp values(100,'A','Job1')"));
sqlList.AddTail(_T("update dept set dname='NEW' where deptno=10"));
sqlList.AddTail(_T("delete from bonus where empno=999"));
CMyODBC db;
db.ConnectDB("daliu", "", "");
db.ExecTrans(sqlList); // 全部成功 Commit,任一失敗 Rollback
db.DisConnect();
// InitInstance 中
char path[MAX_PATH]; GetModuleFileName(NULL, path, MAX_PATH);
CString exePath = path; exePath = exePath.Left(exePath.ReverseFind('\\')+1);
CString mdbPath = exePath + _T("test.mdb");
char cpConfig[MAX_PATH];
strcpy(cpConfig, "DSN=daliu\0");
strcpy(cpConfig+10, "DBQ="); strcpy(cpConfig+14, mdbPath);
strcpy(cpConfig+14+mdbPath.GetLength(), "\0");
strcpy(cpConfig+15+mdbPath.GetLength(), "DEFAULTDIR=");
strcpy(cpConfig+26+mdbPath.GetLength(), exePath);
strcpy(cpConfig+26+mdbPath.GetLength()+exePath.GetLength(), "\0\0");
SQLConfigDataSource(NULL, ODBC_ADD_SYS_DSN,
"Microsoft Access Driver (*.mdb)\0", cpConfig);
關鍵:設定字串以 \0 分隔屬性,結尾需雙 \0\0。
| 優點 | 缺點 |
|---|---|
| 零 Schema 依賴,任意 SQL 即查即用 | 無 ORM 對應,手動拼 SQL 易注入 |
| 動態綁定自動處理型別/長度 | 大量資料時緩衝區固定 256 bytes 可能截斷 |
| 支援事務、預存程序、多種資料源 | 非執行緒安全,連線物件不可跨執行緒共用 |
| 代碼極精簡,易閱讀修改 | 缺乏連線池、參數化查詢、非同步支援 |
適合中小型專案、工具程式、快速原型開發;企業級系統建議改用 ADO/ADO.NET/Entity Framework 等成熟框架。