1. 程式人生 > >利用支援ODBC的CRecordset類實現對資料庫的操作

利用支援ODBC的CRecordset類實現對資料庫的操作

1.MFC中的ODBC類

主要有CDatabase、CRecordset、CRecordview、CDBException、CFieldExchange。這些類封裝了ODBC SDK函式,可以很方便的操作支援ODBC的資料庫。
(1)CDatabase類:封裝應用程式與需要訪問的資料庫之間的連線,控制事務的提交和執行SQL語句的方法。

(2)CRecordset類:封裝大部分操縱資料庫的方法,包括瀏覽、修改記錄、控制遊標移動,排序等操作。CRecordset類是MFC的ODBC類中最重要、功能最強大的一個類。CRecordset類物件提供了從資料來源中提取出的記錄集。在多工作業系統或網路環境中,多個使用者可以共享同一個資料來源。共享資料的一個主要問題是如何協調各個使用者對資料來源的修改。CRecordset提供了幾種不同的方法來處理這個問題,這將由程式採用哪種型別的記錄集來決定。

CRecordset物件通常用於兩種形式:動態行集(dynasets)和快照集(snapshots)。動態行集能與其他使用者所做的更改保持同步,快照集則是資料的一個靜態檢視,當別的使用者改變了記錄時(包括修改、新增和刪除),快照中的記錄不受影響,也就是說,快照不反映別的使用者對資料來源記錄的改變.直到呼叫了CRecordset::Requery重新查詢後,快照才會反映變化。每一種形式在記錄集被開啟時都提供一組記錄,所不同的是,當在一個動態行集裡滾動到一條記錄時,由其他使用者或是應用程式中其他記錄集對該記錄所做的更改會相應地顯示出來,例如在火車聯網銷售系統中,應該實時的顯示共享資料的變化。

(3)CRecordView類:提供與CRecordset物件相連線的檢視,可以建立檢視中的控制元件與資料庫資料的對應,同時支援遊標,修改記錄等操作。

(4)CDBException類:提供對資料庫操作的異常處理。

(5)CFieldExchange類:提供使用者變數與資料庫欄位之間的資料交換。

2.域資料成員與資料交換

 CRecordset類代表一個記錄集。使用者一般用ClassWizard建立一個CRecordset的派生類。ClassWizard可以為派生的記錄集類建立一批資料成員,這些資料成員與記錄的各欄位相對應,被稱為欄位資料成員或域資料成員。域資料成員與表中的欄位名字類似,且型別匹配。
例如:這是一個CRecordset類的派生類的定義
class CSetdata : public CRecordset
{
public:
 CSetdata(CDatabase* pDatabase = NULL);
 DECLARE_DYNAMIC(CSetdata)

// Field/Param Data
 //{{AFX_FIELD(CSetdata, CRecordset)
 //定義域資料成員變數,域資料成員用來與記錄集對應欄位進行資料交換,起到一個緩衝區或中間橋樑的作用。也就是說,我們在處理記錄集時,實際上是對域資料成員進行操作,而不是直接對資料庫中的資料操作。
 CString m_number;               
 CString m_name;
 CString m_sex;
 long m_age;
 //}}AFX_FIELD
 CString number;

// Overrides
 // ClassWizard generated virtual function overrides
 //{{AFX_VIRTUAL(CSetdata)
 public:
 virtual CString GetDefaultConnect();    // Default connection string
 virtual CString GetDefaultSQL();    // Default SQL for Recordset
 virtual void DoFieldExchange(CFieldExchange* pFX);  // RFX support
 //}}AFX_VIRTUAL

// Implementation
#ifdef _DEBUG
 virtual void AssertValid() const;
 virtual void Dump(CDumpContext& dc) const;
#endif
};

#endif 


域資料成員用來儲存某條記錄的各個欄位,它們是程式與記錄之間的緩衝區。域資料成員代表當前記錄,當在記錄集中滾動到某一記錄時,框架自動地把記錄的各個欄位拷貝到記錄集物件的域資料成員中。當用戶要修改當前記錄或增加新記錄時,程式先將各欄位的新值放入域資料成員中,然後呼叫相應的CRecordset成員函式把域資料成員設定到資料來源中。

  不難看出,在記錄集與資料來源之間有一個數據交換問題.CRecordset類使用"記錄域交換"(Record Field Exchange,縮寫為RFX)機制自動地在域資料成員和資料來源之間交換資料。RFX機制與對話資料交換(DDX)類似。CRecordset的成員函式DoFieldExchange負責資料交換任務,在該函式中呼叫了一系列RFX函式。當用戶用ClassWizard加入域資料成員時,ClassWizard會自動在DoFieldExchange中建立RFX。
典型的DoFieldExchange函式

void CSetdata::DoFieldExchange(CFieldExchange* pFX)
{
 //{{AFX_FIELD_MAP(CSetdata)
 pFX->SetFieldType(CFieldExchange::outputColumn);
 RFX_Text(pFX, _T("[number]"), m_number);        //實現了域資料成員和資料來源之間的資料交換
 RFX_Text(pFX, _T("[name]"), m_name);
 RFX_Text(pFX, _T("[sex]"), m_sex);
 RFX_Long(pFX, _T("[age]"), m_age);
  //}}AFX_FIELD_MAP
 pFX->SetFieldType(CFieldExchange::param);
 RFX_Text(pFX,_T("[]"),number);
}

3.記錄集的建立和關閉

使用MFC的ODBC類操作資料庫首先要建立一個CRecordset的派生類來關聯對應的資料表,然後呼叫Open()函式來查詢資料來源中的資料並建立記錄集。此外,在Open函式中,可能還會呼叫GetDefaultConnect和GetDefaultSQL函式。
(1)首先在派生類的建構函式中會有一個引數指向一個CDatabase物件,用來獲取資料來源。函式如下:
CRecordset(CDatabase* pDatabase = NULL);
如果pDatabase為NULL,則會在Open函式中自動構建一個CDatabase物件。如果CDatabase物件還未與資料來源連線,那麼在Open函式中會建立連線,連線字串由成員函式GetDefaultConnect提供。

(2)virtual CString GetDefaultConnect();
該函式返回預設的連線字串。Open函式在必要的時侯會呼叫該函式獲取連線字串以建立與資料來源的連線。一般需要在CRecordset派生類中覆蓋該函式並在新版的函式中提供連線字串。
例如:CSetdata為CRecordset類的派生類
CString CSetdata::GetDefaultConnect()
{
 return _T("ODBC;DSN=my data");
}
這個函式獲取了資料庫連線字串,包括連線方式以及資料來源的名稱。

(3)virtual CString GetDefaultSQL();
Open函式在必要時會呼叫該函式返回預設的SQL語句或表名以查詢資料來源中的記錄。一般需要在CRecordset派生類中覆蓋該函式並在新版的函式中提供SQL語句或表名。
例如:
CString CSetdata::GetDefaultSQL()
{
 return _T("[bingli]");
}
這個類返回的是資料來源中資料表bingli的名稱。

(4)virtual BOOL Open( UINT nOpenType = AFX_DB_USE_DEFAULT_TYPE, LPCTSTR lpszSQL = NULL, DWORD dwOptions = none );
throw( CDBException, CMemoryException );
該函式使用指定的SQL語句查詢資料來源中的記錄並按指定的型別和選項建立記錄集。引數nOpenType說明了記錄集的型別,如下表所示。如果要求的型別驅動程式不支援,則函式將產生一個異常。引數lpszSQL是一個SQL的SELECT語句,或是一個表名。函式用lpszSQL來進行查詢,如果該引數為NULL,則函式會呼叫GetDefaultSQL獲取預設的SQL語句。引數dwOptions可以是一些選項的組合,常用的選項在下表中列出。若建立成功則函式返回TRUE,若函式呼叫了CDatabase::Open且返回FALSE,則函式返回FALSE.

記錄集的型別
型別                        含義 
AFX_DB_USE_DEFAULT_TYPE     使用預設值. 
CRecordset::dynaset         可雙向滾動的動態集. 
CRecordset::snapshot        可雙向滾動的快照. 
CRecordset::dynamic         提供比動態集更好的動態特性,大部分ODBC驅動程式不支援這種記錄集. 
CRecordset::forwardOnly     只能前向滾動的只讀記錄集. 
 
建立記錄集時的常用選項  
選項                           含義 
CRecordset::none               無選項(預設). 
CRecordset::appendOnly         不允許修改和刪除記錄,但可以新增記錄. 
CRecordset::readOnly           記錄集是隻讀的. 
CRecordset::skipDeletedRecords 有些資料庫(如FoxPro)在刪除記錄時並不真刪除,而是做個刪除標記,在滾動時將跳過這些被刪除的記錄。
 
如果所有的引數都為空,例如:
CSetdata *pset = new CSetdata();   
pset->Open();
那麼Open()函式將會呼叫GetDefaultSQL()函式獲取指定資料表的資料並建立資料集。實際上,如果只提供表名,CRecordset類會構造一個SELECT語句來查詢資料來源。例如:SELECT (能提供的欄位名) FROM bingli(即GetDefaultSQL()函式返回的表名)。

(5) 建立記錄集後,使用者可以隨時呼叫Requery成員函式來重新查詢和建立記錄集。Requery有兩個重要用途:

使記錄集能反映使用者對資料來源的改變

按照新的過濾或排序方法查詢記錄並重新建立記錄集.

  在呼叫Requery之前,可呼叫CanRestart來判斷記錄集是否支援Requery操作。要記住Requery只能在成功呼叫Open後呼叫,所以程式應呼叫IsOpen來判斷記錄集是否已建立.函式的宣告為

virtual BOOL Requery( );
throw( CDBException, CMemoryException );
返回TRUE表明記錄集建立成功,否則返回FALSE。若函式內部出錯則產生異常.

BOOL CanRestart( ) const; //若支援Requery則返回TRUE
BOOL IsOpen( ) const; //若記錄集已建立則返回TRUE

  CRecordset類有兩個公共資料成員m_strFilter和m_strSort用來設定對記錄的過濾和排序。在呼叫Open或Requery前,如果在這兩個資料成員中指定了過濾或排序,那麼Open和Requery將按這兩個資料成員指定的過濾和排序來查詢資料來源。
   成員m_strFilter用於指定過濾器。m_strFilter實際上包含了SQL的WHERE子句的內容,但它不含WHERE關鍵字。使用m_strFilter的一個例子為:
m_pSet->m_strFilter=“CourseID=‘MATH101’”; //只選擇CourseID為MATH101的記錄
if(m_pSet->Open(CRecordset::snapshot, “Section”))

. . . . . .

  成員m_strSort用於指定排序.m_strSort實際上包含了ORDER BY子句的內容,但它不含ORDER BY關鍵字.m_strSort的一個例子為

m_pSet->m_strSort=“CourseID DESC”; //按CourseID的降序排列記錄

m_pSet->Open();

. . . . . .

  事實上,Open函式在構造SELECT語句時,會把m_strFilter和m_strSort的內容放入SELECT語句的WHERE和ORDER BY子句中.如果在Open的lpszSQL引數中已包括了WHERE和ORDER BY子句,那麼m_strFilter和m_strSort必需為空.

  呼叫無引數成員函式Close可以關閉記錄集.在呼叫了Close函式後,程式可以再次呼叫Open建立新的記錄集.CRecordset的解構函式會呼叫Close函式,所以當刪除CRecordset物件時記錄集也隨之關閉。

4.滾動記錄

CRecordset提供了幾個成員函式用來在記錄集中滾動,如下所示.當用這些函式滾動到一個新記錄時,框架會自動地把新記錄的內容拷貝到域資料成員中。

void MoveNext();  //前進一個記錄
void MovePrev();  //後退一個記錄
void MoveFirst(); //滾動到記錄集中的第一個記錄
void MoveLast();  //滾動到記錄集中的最後一個記錄
void SetAbsolutePosition( long nRows ); 
該函式用於滾動到由引數nRows指定的絕對位置處.若nRows為負數,則從後往前滾動.例如,當nRows為-1時,函式就滾動到記錄集的末尾。注意,該函式不會跳過被刪除的記錄。

virtual void Move( long nRows, WORD wFetchType = SQL_FETCH_RELATIVE );
該函式功能強大.通過將wFetchType引數指定為SQL_FETCH_NEXT、SQL_FETCH_PRIOR、SQL_FETCH_FIRST、SQL_FETCH_LAST和SQL_FETCH_ABSOLUTE,可以完成上面五個函式的功能.若wFetchType為SQL_FETCH_RELATIVE,那麼將相對當前記錄移動,若nRows為正數,則向前移動,若nRows為負數,則向後移動。 

  如果在建立記錄集時選擇了CRecordset::skipDeletedRecords選項,那麼除了SetAbsolutePosition外,在滾動記錄時將跳過被刪除的記錄,這一點物件FoxPro這樣的資料庫十分重要。

 如果記錄集是空的,那麼呼叫上述函式將產生異常。另外,必須保證滾動沒有超出記錄集的邊界。呼叫IsEOF和IsBOF可以進行這方面的檢測。

BOOL IsEOF( ) const; //如果記錄集為空或滾動過了最後一個記錄,那麼函式返回TRUE,否則返回FALSE。 
BOOL IsBOF( ) const; //如果記錄集為空或滾動過了第一個記錄,那麼函式返回TRUE,否則返回FALSE。

下面是一個使用IsEOF的例子:
while(!m_pSet->IsEOF( ))
m_pSet->MoveNext( );

呼叫GetRecordCound可獲得記錄集中的記錄總數,該函式的宣告為:
long GetRecordCount( ) const;  
要注意這個函式返回的實際上是使用者在記錄集中滾動的最遠距離.要想真正返回記錄總數,只有呼叫MoveNext移動到記錄集的末尾(MoveLast不行)。
 

5.修改、新增和刪除記錄

要修改當前記錄,應該按下列步驟進行:

呼叫Edit成員函式.呼叫該函式後就進入了編輯模式,程式可以修改域資料成員.注意不要在一個空的記錄集中呼叫Edit,否則會產生異常.

Edit函式會把當前域資料成員的內容儲存在一個緩衝區中,這樣做有兩個目的,一是可以與域資料成員作比較以判斷哪些欄位被改變了,二是在必要的時侯可以恢復域資料成員原來的值.若再次呼叫Edit,則將從緩衝區中恢復域資料成員,呼叫後程序仍處於編輯模式.呼叫Move(AFX_MOVE_REFRESH)或Move(0)可退出編輯模式(AFX_MOVE_REFRESH的值為0),同時該函式會從緩衝區中恢復域資料成員.

設定域資料成員的新值.

呼叫Update完成編輯.Update把變化後的記錄寫入資料來源並結束編輯模式. 

要向記錄集中新增新的記錄,應該按下列步驟進行:

呼叫AddNew成員函式.呼叫該函式後就進入了新增模式,該函式把所有的域資料成員都設定成NULL(注意,在資料庫術語中,NULL是指沒有值,這與C++的NULL是不同的).與Edit一樣,AddNew會把當前域資料成員的內容儲存在一個緩衝區中,在必要的時侯,程式可以再次呼叫AddNew取消新增操作並恢復域資料成員原來的值,呼叫後程序仍處於新增模式.呼叫Move(AFX_MOVE_REFRESH)可退出新增模式,同時該函式會從緩衝區中恢復域資料成員.

設定域資料成員.

呼叫Update.Update把域資料成員中的內容作為新記錄寫入資料來源,從而結束了新增. 

如果記錄集是快照,那麼在新增一個新的記錄後,需要呼叫Requery重新查詢,因為快照無法反映新增操作.

要刪除記錄集的當前記錄,應按下面兩步進行:

呼叫Delete成員函式.該函式會同時給記錄集和資料來源中當前記錄加上刪除標記.注意不要在一個空記錄集中呼叫Delete,否則會產生一個異常.

滾動到另一個記錄上以跳過刪除記錄. 

上面提到的函式宣告為:

virtual void Edit( );throw( CDBException, CMemoryException );

virtual void AddNew( );throw( CDBException );

virtual void Delete( );throw( CDBException );

virtual BOOL Update( );throw( CDBException ); 
若更新失敗則函式返回FALSE,且會產生一個異常.

   在對記錄集進行更改以前,程式也許要呼叫下列函式來判斷記錄集是否是可以更改的,因為如果在不能更改的記錄集中進行修改、新增或刪除將導致異常的產生.

BOOL CanUpdate( ) const; //返回TRUE表明記錄是可以修改、新增和刪除的.

BOOL CanAppend( ) const; //返回TRUE則表明可以新增記錄.