相較 ADO/DAO 的高層封裝,ODBC API 雖代碼較繁,但具備:
odbc32.lib、odbccp32.lib,無需 MDAC/ACE 提供者。
#include
#include
#include // SQLConfigDataSource
#pragma comment(lib, "odbc32.lib")
#pragma comment(lib, "odbccp32.lib")
BOOL ConfigDSN() {
SQLRETURN ret = SQLConfigDataSource(NULL, ODBC_ADD_SYS_DSN,
_T("SQL Server"),
_T("DSN=MyDSN\0Server=(local)\0Database=master\0\0"));
return (ret == TRUE);
}
\0 結尾,屬性間單 \0 分隔。ODBC_REMOVE_SYS_DSN 移除、ODBC_CONFIG_SYS_DSN 修改。
SQLHENV hEnv = SQL_NULL_HANDLE;
SQLHDBC hDbc = SQL_NULL_HANDLE;
auto check = [](SQLRETURN rc, SQLSMALLINT handleType, SQLHANDLE handle, const char* msg) {
if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
SQLWCHAR sqlState[6], msgBuf[1024];
SQLINTEGER native;
SQLSMALLINT len;
SQLGetDiagRec(handleType, handle, 1, sqlState, &native, msgBuf, 1024, &len);
// 記錄錯誤...
return false;
}
return true;
};
if (!check(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv), ...)) return FALSE;
if (!check(SQLSetEnvAttr(hEnv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0), ...)) return FALSE;
if (!check(SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDbc), ...)) return FALSE;
SQLRETURN ret = SQLConnect(hDbc,
(SQLTCHAR*)dsn, SQL_NTS,
(SQLTCHAR*)uid, SQL_NTS,
(SQLTCHAR*)pwd, SQL_NTS);
if (!check(ret, SQL_HANDLE_DBC, hDbc, "Connect")) {
SQLFreeHandle(SQL_HANDLE_DBC, hDbc);
SQLFreeHandle(SQL_HANDLE_ENV, hEnv);
return FALSE;
}
// 連線成功,保存 hEnv/hDbc 供後續使用
BOOL ExecuteDDL(SQLHDBC hDbc, const CString& sql) {
SQLHSTMT hStmt;
if (!check(SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt), ...)) return FALSE;
SQLRETURN ret = SQLExecDirect(hStmt, (SQLTCHAR*)(LPCTSTR)sql, SQL_NTS);
BOOL ok = check(ret, SQL_HANDLE_STMT, hStmt, "ExecDirect");
SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
return ok;
}
// 範例
ExecuteDDL(hDbc, _T("CREATE TABLE Users (Id INT PRIMARY KEY, Name NVARCHAR(50))"));
ExecuteDDL(hDbc, _T("DROP TABLE OldTable"));
ExecuteDDL(hDbc, _T("ALTER TABLE Users ADD Email NVARCHAR(100)"));
BOOL InsertUser(SQLHDBC hDbc, int id, const CString& name) {
SQLHSTMT hStmt;
SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt);
SQLPrepare(hStmt, (SQLTCHAR*)_T("INSERT INTO Users (Id, Name) VALUES (?, ?)"), SQL_NTS);
SQLINTEGER cbId = 0, cbName = SQL_NTS;
SQLBindParameter(hStmt, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, &id, 0, &cbId);
SQLBindParameter(hStmt, 2, SQL_PARAM_INPUT, SQL_C_TCHAR, SQL_WVARCHAR, 50, 0,
(SQLPOINTER)(LPCTSTR)name, 0, &cbName);
SQLRETURN ret = SQLExecute(hStmt);
BOOL ok = check(ret, SQL_HANDLE_STMT, hStmt, "Execute");
SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
return ok;
}
BOOL QueryUsers(SQLHDBC hDbc, std::vector>& out) {
SQLHSTMT hStmt;
SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt);
SQLExecDirect(hStmt, (SQLTCHAR*)_T("SELECT Id, Name FROM Users"), SQL_NTS);
int id; CString name;
SQLLEN cbId, cbName;
SQLBindCol(hStmt, 1, SQL_C_LONG, &id, sizeof(id), &cbId);
SQLBindCol(hStmt, 2, SQL_C_TCHAR, name.GetBuffer(100), 100*sizeof(TCHAR), &cbName);
while (SQLFetch(hStmt) == SQL_SUCCESS) {
name.ReleaseBuffer();
out.emplace_back(id, name);
name.GetBuffer(100); // 重置緩衝
}
name.ReleaseBuffer();
SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
return true;
}
同插入,改用 UPDATE ... SET ... WHERE ? / DELETE ... WHERE ? 並綁定參數執行 SQLExecute。
master 資料庫。sysdatabases 確認目標資料庫是否存在:SELECT name FROM sysdatabases WHERE name='MyDB'。CREATE DATABASE MyDB。MyDB(SQLSetConnectAttr 或重新 SQLConnect)。sysobjects 確認資料表是否存在,缺則建立。此模式適用於安裝程式、首次啟動自動初始化 Schema 等場景。
SQLFreeHandle(SQL_HANDLE_STMT, hStmt); // 每個陳述式用完即釋
SQLDisconnect(hDbc);
SQLFreeHandle(SQL_HANDLE_DBC, hDbc);
SQLFreeHandle(SQL_HANDLE_ENV, hEnv);
| 問題 | 解決 |
|---|---|
| 中文亂碼 | 連線字串加 CharacterSet=UTF-8,或使用 Unicode 版 API (SQLWCHAR/SQLExecDirectW) |
| 連線逾時 | SQLSetConnectAttr(hDbc, SQL_LOGIN_TIMEOUT, (SQLPOINTER)30, 0) |
| 游標類型不支援 | SQLSetStmtAttr(hStmt, SQL_ATTR_CURSOR_TYPE, (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY, 0) |
| 參數綁定類型不匹配 | 嚴格對應 SQL_C_* (C 類型) ↔ SQL_* (SQL 類型),參考 MSDN 附錄 D |
| 記憶體洩漏 | 每個 AllocHandle 必配 FreeHandle,建議封裝 RAII 類別 |