基於ODBC API實現對資料庫的訪問
Visual C++提供了多種多樣的資料庫訪問技術,ODBC API,MFC ODBC,DAO,OLEDB、ADO等。這些技術各有自己的特點,他們提供了簡單、靈活、訪問速度快、可擴充套件性強的開發技術,而這些正是Visual C++和其他開發工具相比優勢所在。ODBC API是為客戶應用程式訪問關係資料庫時提供了一個的標準介面,對不同的資料庫,ODBC提供了一套統一的API,使得應用程式可以應用所提供的API,訪問任何提供了ODBC驅動程式的資料庫。而且,ODBC已經成為了一種標準,所以現在幾乎所有的關係型資料庫都提供了ODBC驅動程式,從而使得ODBC的應用更加廣泛。ODBC API可以進行一些底層的資料庫操作,訪問速度快捷,使用靈活,但編碼相對來說比較複雜。對於一個刨根究底的研究型愛好者來說,卻是一個很好的選擇。同時也讓大家對於ADO、DAO的底層封裝機制有個初步的瞭解和認識。
1、ODBC資料來源的建立
從控制面板中雙擊管理工具圖示,然後在新出現的視窗中雙擊資料來源(ODBC)。在彈出的對話方塊中選擇不同的選項卡來確定建立資料來源的型別。這樣手工配置資料來源我個人感覺比較麻煩,是不是可以用API來自動實現配置呢?答案是肯定的。配置資料來源的程式碼如下:
- SQLRETURN retcode;
- retcode = SQLConfigDataSource(NULL,ODBC_ADD_SYS_DSN,"SQL Server","DSN=master/0Server=(local)/0Database=master/0/0");
- if(!retcode)
- {
- AfxMessageBox(
- return FALSE;
- }
2、連線資料來源
在Visual C++程式中使用剛才建立的資料來源之前,還必須建立一個到資料來源的連線。以連線master資料庫為例,實現一個數據源的連線。程式碼如下:
- // 連線資料來源
- BOOL CDbLink::OpenDatabase()
- {
- SQLINTEGER cbLenth = 0 ;
- SQLRETURN retcode;
- retcode = SQLConfigDataSource(NULL,ODBC_ADD_SYS_DSN,"SQL Server","DSN=master/0Server=(local)/0Database=master/0/0"
- if(!retcode)
- {
- AfxMessageBox("系統資料來源配置失敗!");
- return FALSE;
- }
- retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv) ;
- if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO)
- {
- retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
- if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO)
- {
- retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
- if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO)
- {
- retcode = SQLConnect(hdbc, (SQLCHAR*)(LPCTSTR)m_strDSN, SQL_NTS, (SQLCHAR*)(LPCTSTR)m_strUSER, SQL_NTS,
- (SQLCHAR*)(LPCTSTR)m_strPWD, SQL_NTS);
- if (!(retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO))
- {
- AfxMessageBox("資料庫連線失敗!") ;
- SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
- SQLFreeHandle(SQL_HANDLE_ENV, henv);
- return FALSE;
- }
- else
- {
- m_bLink = TRUE;
- return TRUE;
- }
- }
- else
- {
- AfxMessageBox("連線控制代碼分配出錯") ;
- SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
- SQLFreeHandle(SQL_HANDLE_ENV, henv);
- return FALSE;
- }
- }
- else
- {
- AfxMessageBox("屬性設定出錯!") ;
- SQLFreeHandle(SQL_HANDLE_ENV, henv);
- return FALSE;
- }
- }
- else
- {
- AfxMessageBox("環境變數分配出錯!") ;
- SQLFreeHandle(SQL_HANDLE_ENV, henv);
- return FALSE;
- }
- }
其中,連線資料來源中比較關鍵的是:資料來源名DSN、使用者名稱和密碼。
3、動態建立資料庫
這裡先動態建立一個數據庫,然後我們接下來以這個資料庫來分析資料庫的表的操作過程。那如何動態建立資料庫呢?這個可能有點挑戰吧!master資料庫控制SQL Server的所有方面,這個資料庫中包括所有的配置資訊、使用者登入資訊、當前正在伺服器中執行的過程的資訊,當然要想建立資料庫還是得靠他了,這個資料庫中有一張系統表sysdatabases就是用來專門負責登記所有資料庫的情況。所以你需要為master配置一個數據源,然後直接連線到這個資料來源後,執行建立資料庫的SQL語句就可以建立一個數據庫了,當然在建立資料庫之前你必須得到系統表sysdatabases查詢看看,你的資料庫是不是已經存在了,如果已經存在了就不必再建立了,明白了原理後,其實也很簡單的,現在讓我們踏上建立資料庫的美麗旅程吧!程式碼如下:
- // 判斷我們所建立的資料庫是否已經存在
- BOOL CDbLink::IsDatabaseExisted(CString strDbName)
- {
- SQLHSTMT hstmt ;
- SQLRETURN retcode;
- SQLINTEGER cbLenth = 0 ;
- CString strSQL;
- strSQL.Format("SELECT * FROM sysdatabases WHERE name='%s'", strDbName);
- retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
- if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO)
- {
- if (SQLExecDirect(hstmt, (SQLCHAR*)(LPCTSTR)strSQL, SQL_NTS) == SQL_ERROR)
- {
- SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
- return FALSE;
- }
- if((SQLFetch(hstmt) == SQL_SUCCESS) || (SQLFetch(hstmt) == SQL_SUCCESS_WITH_INFO))
- {
- SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
- return TRUE;
- }
- else
- {
- SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
- return FALSE;
- }
- }
- else
- {
- SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
- return FALSE;
- }
- }
- // 建立資料庫,其中strDbName代表資料庫名
- BOOL CDbLink::CreateDatabase(CString strDbName)
- {
- SQLHSTMT hstmt ;
- SQLRETURN retcode;
- SQLINTEGER cbLenth = 0 ;
- BOOL bIsExisted = IsDatabaseExisted(strDbName);
- if(bIsExisted)
- {
- return TRUE;
- }
- else
- {
- CString strSQL;
- strSQL.Format("CREATE DATABASE [%s] ON (NAME = N'%s_dat', FILENAME = N'D://%s.mdf' ,SIZE = 39, FILEGROWTH = 2) LOG ON (NAME = N'%s_log', FILENAME = N'D://%s.ldf' , SIZE = 2, FILEGROWTH = 1) COLLATE Chinese_PRC_CI_AS",
- strDbName,strDbName,strDbName,strDbName,strDbName);
- retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
- if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO)
- {
- if (SQLExecDirect(hstmt, (SQLCHAR*)(LPCTSTR)strSQL, SQL_NTS) != SQL_ERROR)
- {
- SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
- return TRUE;
- }
- else
- {
- SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
- return FALSE;
- }
- }
- else
- {
- SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
- return FALSE;
- }
- }
- }
4、建立表
上面我們已經在SQL SERVER中建立了一個新的資料庫,當然這個資料庫除了裡面的系統表以外,別無它表。我們得自己在裡面建立一個表。如何建立一個表呢?同樣,這個時候我們得連線到我們新建立的資料庫的資料來源中,然後再執行建立表的SQL語句就可以建立一張表了,當然在建立表之前同樣得查詢看看這個表是否已經存在了。配置資料來源的部分仿照上面的步驟進行即可。建立表的SQL語句如下:
- CREATE TABLE [Obj_User]
- (
- [User_Iden] [int] NOT NULL ,
- [User_Nina] [varchar] (32) COLLATE Chinese_PRC_CI_AS NOT NULL ,
- [User_Pawo] [varchar] (32) COLLATE Chinese_PRC_CI_AS NOT NULL ,
- [User_RoId] [int] NOT NULL ,
- [User_Name] [varchar] (32) COLLATE Chinese_PRC_CI_AS NULL ,
- [User_OfPh] [varchar] (32) COLLATE Chinese_PRC_CI_AS NULL ,
- [User_MoPh] [varchar] (32) COLLATE Chinese_PRC_CI_AS NULL ,
- [User_Mail] [varchar] (64) COLLATE Chinese_PRC_CI_AS NULL ,
- [User_QQ] [varchar] (16) COLLATE Chinese_PRC_CI_AS NULL ,
- [User_Addr] [varchar] (256) COLLATE Chinese_PRC_CI_AS NULL ,
- [User_Phot] [varchar] (256) COLLATE Chinese_PRC_CI_AS NULL ,
- [User_Born] [datetime] NULL ,
- [User_Job] [varchar] (64) COLLATE Chinese_PRC_CI_AS NULL ,
- [User_ShId] [int] NULL ,
- CONSTRAINT [PK_Obj_User] PRIMARY KEY CLUSTERED ([User_Iden]) ON [PRIMARY]
- ) ON [PRIMARY]
建立的表格名為Obj_User,如下:
欄位名 | 資料型別 | 主鍵 | 允許空 | 備註 |
User_Iden | int | * | *(*代表不允許) | 使用者ID |
User_Nina | varchar(32) | * | 使用者暱稱 | |
User_Pawo | varchar(32) | * | 使用者密碼 | |
User_RoId | int | * | 使用者角色 | |
User_Name | varchar(32) | 使用者名稱稱 | ||
User_OfPh | varchar(32) | 辦公號碼 | ||
User_MoPh | varchar(32) | 手機號碼 | ||
User_Mail | varchar(64) | 使用者郵箱 | ||
User_QQ | varchar(16) | 使用者QQ | ||
User_Addr | varchar(256) | 使用者地址 | ||
User_Phot | varchar(256) | 使用者相片 | ||
User_Born | datetime | 使用者生日 | ||
User_Job | varchar(32) | 使用者工作 | ||
User_ShId | int | 使用者店鋪 |
- // 先判斷表是否已經存在,dbmarket資料庫的系統表sysobjects記錄了當前所建立的所有使用者表。
- BOOL CDbOperator::IsTableExisted(CString strTableName)
- {
- SQLHSTMT hstmt ;
- SQLRETURN retcode;
- SQLINTEGER cbLenth = 0 ;
- CString strSQL;
- strSQL.Format("SELECT * FROM sysobjects WHERE name='%s'", strTableName);
- retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
- if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO)
- {
- if (SQLExecDirect(hstmt, (SQLCHAR*)(LPCTSTR)strSQL, SQL_NTS) == SQL_ERROR)
- {
- SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
- return FALSE;
- }
- if((SQLFetch(hstmt) == SQL_SUCCESS) || (SQLFetch(hstmt) == SQL_SUCCESS_WITH_INFO))
- {
- SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
- return TRUE;
- }
- else
- {
- SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
- return FALSE;
- }
- }
- else
- {
- SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
- return FALSE;
- }
- }
- // 執行建立表操作
- BOOL CDbOperator::CreateSQL(CString strSQL)
- {
- SQLHSTMT hstmt ;
- SQLRETURN retcode;
- SQLINTEGER cbLenth = 0 ;
- retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
- if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO)
- {
- if (SQLExecDirect(hstmt, (SQLCHAR*)(LPCTSTR)strSQL, SQL_NTS) != SQL_ERROR)
- {
- SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
- return TRUE;
- }
- else
- {
- SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
- return FALSE;
- }
- }
- else
- {
- SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
- return FALSE;
- }
- }
4、訪問表
更新、插入、刪除語句都比較簡單,直接執行SQL語句即可實現,現在就Select情況做如下說明:
1、準備SQL語句。
2、將指定的變數與列繫結,記住你成功的關鍵在於你對SQL中繫結型別的熟練程度。
3、提取資料,每提取一個記錄後,遊標自動向後移動,直到移動到最後時返回。
為了提高程式碼的可重用性,應該宣告一個的標頭檔案並提供統一資料庫介面。根據上面的表模型,設計的結構體USER,程式碼如下:
- #ifndef __DBDEFINE_H__
- #define __DBDEFINE_H__
- typedefstruct _USER
- {
- int Iden;
- char Nina[32];
- char Pawo[32];
- int RoId;
- char Name[32];
- char OfPh[32];
- char MoPh[32];
- char Mail[64];
- char QQ[16];
- char Addr[256];
- char Phot[256];
- SQL_TIMESTAMP_STRUCT Born;
- char Job[32];
- int ShId;
- }USER, *PUSER;
- #endif
- // 根據使用者ID獲取使用者資訊
- BOOL CDbOperator::GetUserByUserID(int nUserId, USER* pUser)
- {
- SQLHSTMT hstmt ;
- SQLRETURN retcode;
- SQLINTEGER cbLenth = 0 ;
- ZeroMemory(pUser, sizeof(USER));
- pUser->Iden = nUserId;
- CString strSQL;
- strSQL.Format("SELECT User_Nina, User_Pawo, User_RoId, User_Name, User_OfPh, User_MoPh, User_Mail, User_QQ, User_Addr,/
- User_Phot, User_Born, User_Job, User_ShId FROM Obj_User WHERE User_Iden=%d", nUserId);
- retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
- if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO)
- {
- SQLBindCol(hstmt, 1, SQL_C_CHAR, (SQLPOINTER)pUser->Nina, 32, &cbLenth);
- SQLBindCol(hstmt, 1, SQL_C_CHAR, (SQLPOINTER)pUser->Pawo, 32, &cbLenth);
- SQLBindCol(hstmt, 1, SQL_C_ULONG, (SQLPOINTER)&pUser->RoId, 4, &cbLenth);
- SQLBindCol(hstmt, 1, SQL_C_CHAR, (SQLPOINTER)pUser->Name, 32, &cbLenth);
- SQLBindCol(hstmt, 1, SQL_C_CHAR, (SQLPOINTER)pUser->OfPh, 32, &cbLenth);
- SQLBindCol(hstmt, 1, SQL_C_CHAR, (SQLPOINTER)pUser->MoPh, 32, &cbLenth);
- SQLBindCol(hstmt, 1, SQL_C_CHAR, (SQLPOINTER)pUser->Mail, 64, &cbLenth);
- SQLBindCol(hstmt, 1, SQL_C_CHAR, (SQLPOINTER)pUser->QQ, 16, &cbLenth);
- SQLBindCol(hstmt, 1, SQL_C_CHAR, (SQLPOINTER)pUser->Addr, 256, &cbLenth);
- SQLBindCol(hstmt, 1, SQL_C_CHAR, (SQLPOINTER)pUser->Phot, 256, &cbLenth);
- SQLBindCol(hstmt, 1, SQL_C_TYPE_TIMESTAMP, (SQLPOINTER)&pUser->Born, sizeof(SQL_C_TYPE_TIMESTAMP), &cbLenth);
- SQLBindCol(hstmt, 1, SQL_C_CHAR, (SQLPOINTER)pUser->Job, 32, &cbLenth);
- SQLBindCol(hstmt, 1, SQL_C_ULONG, (SQLPOINTER)&pUser->ShId, 4, &cbLenth);
- if (SQLExecDirect(hstmt, (SQLCHAR*)(LPCTSTR)strSQL, SQL_NTS) == SQL_ERROR)
- {
- AfxMessageBox(strSQL) ;
- SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
- return FALSE;
- }
- retcode = SQLFetch(hstmt);
- if(retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO)
- {
- }
- else
- {
- AfxMessageBox(strSQL) ;
- SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
- return FALSE;
- }
- SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
- return TRUE;
- }
- else
- {
- AfxMessageBox(strSQL) ;
- SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
- return FALSE;
- }
- }
總結:
用ODBC API來訪問資料庫其實是一件比較簡單的事情,做完一次後,以後複製就可以了。設計上時候,資料庫的部分應該先設計好各種共同的結構宣告,然後實現對資料庫的介面,以DLL方式提供給使用者。使用者不必再考慮資料庫的訪問問題,若資料庫接口出現錯誤,直接跟資料庫編寫的人聯絡就OK。這樣資料庫的訪問就統一了,避免了各個程式設計師都要寫資料庫訪問介面的麻煩。但呼叫時候必須有共同的資料庫的結構宣告,以及你提供的資料庫介面函式的說明。呼叫者只要嵌入標頭檔案後,產生資料庫物件即可訪問,因為我的設計是將資料庫的連線寫在建構函式裡,呼叫程式碼如下:
- #include "DbOperator/DbOperator.h"
- CDbOperator dbOperator;// 建構函式實現連線
- if(!dbOperator.m_bLink)
- {
- AfxMessageBox("資料庫連線不成功!");
- return FALSE;
- }
- dbOperator.InitAllTable(); // 統一的資料庫介面函式,實現從檔案中讀取SQL語句,執行建立比操作。
- dbOperator.InitTableContent();// 從檔案中讀取你想要初始化的內容寫入到我們所建立的表中。
至於其他的訪問,比如修改表的欄位、Select一個image的內容等相對複雜操作,大家可以查閱相關資料獲取。
動態建立資料庫的實現效果如下: