因為MFC完全支持數據庫應用程序的開發,所以大多數數據庫應用都使用CDatabase和CRecordset類,並且類向導(Class Wizard)提供了快速簡易的方式來使用這兩個類。有一點不足的就是當應用程序涉及到多表數據庫時,類向導將產生大量的關於記錄集的源碼文件使得工程給人的感覺很臃腫混亂。
本文介紹如何使用一個模板記錄集類來降低類向導所產生的記錄集文件的數量,同時增強記錄集類(CRecordset)的功能。這個模板記錄集類叫做:CDataSet。它的主要目的是降低代碼量,為數據對象數組提供一個接口。
CDataSet類定義如下:
CDataSet 頭文件
template class CDataSet : public CRecordset
{
public:
CDataSet(LPCSTR Table, CDatabase* pdb);
T m_Data; // Attached object
CString m_DefaultSQL; // Default SQL SELECT statement
CString m_DefaultSort; // Default SQL ORDER BY clause
CString m_DefaultFilter; // Default SQL WHERE clause
// Operations
public:
virtual BOOL Search(LPCSTR Filter, LPCSTR Sort = NULL, BOOL bFail = FALSE);
virtual BOOL DirectSearch(LPCSTR Filter, LPCSTR Sort = NULL, BOOL bFail = FALSE);
virtual void LoadAll(CArray& A, int N = 0);
virtual void SaveAll(CArray& A);
virtual void Load(T& Data) { Data = m_Data; }
virtual void Store(T& Data);
// Implementation
protected:
virtual CString GetDefaultSQL() { return m_DefaultSQL; }
virtual void DoFieldExchange(CFieldExchange* pFX);
};
這個模板有兩個參數:一個是數據對象類,另一個是綁定字段的數量。數據對象類由一組成員變量組成,對應著不同的數據庫表的字段,例如: struct DataObj {
CString Var1;
CString Var2;
int Var3;
float Var4;
// ......
// 成員函數
};
如果將數據對象定義成一個類,而不是一個結構,就必須定義缺省的供類模板使用的構造函數並重載 “=” 操作符(operator=),要不然的話編譯會出錯。CDataSet的成員變量m_DefaultSQL用來靈活方便地控制SQL數據源,m_DefaultSQL可以是一個表名、表名的列表、或者是任何復雜的SQL語句,還有就是因為m_DefaultSQL可以在運行時被改變,使得相同的類訪問不同的表(如果這些表有相同的結構)成為可能。如果默認的SQL發生變化,相應的記錄集必須被重新打開。 Load(), Store(), LoadAll() 和 StoreAll() 方法完成單個或多個數據對象的加載和存儲操作。LoadAll()的最後一個參數,N,指定要加載記錄數的最大值。Search()和 DirectSearch()方法實現改進的搜索能力。Search()使用m_DefaultSort 和 m_DefaultFilter成員變量並且當希望的記錄未找到和 bFail為TRUE時丟出異常。DirectSearch()接受一個外部指定的SQL WHERE 從句。以下是這個模板類的實現,它不是很復雜: CDataSet實現
template<class T, int M> CDataSet<T, M>::CDataSet(LPCSTR Table, CDatabase* pdb) :
CRecordset(pdb)
{
m_nFields = M;
m_DefaultSQL = Table;
m_DefaultFilter = "%s";
}
template<class T, int M> BOOL CDataSet<T, M>::Search(LPCSTR Filter,LPCSTR Sort, BOOL bFail)
{
if ( IsOpen() )
Close();
SetStatus("Opening " + m_DefaultSQL + " ...");
if ( Filter )
m_strFilter.Format(m_DefaultFilter, Filter);
else
m_strFilter = "";
m_strSort = Sort;
Open();
// Throw exception if record not found
if ( bFail && IsEOF() )
THROW(new CMyException(m_DefaultSQL + " record not found!"));
return !IsEOF();
}
template<class T, int M> BOOL CDataSet<T, M>::DirectSearch(LPCSTR Filter, LPCSTR Sort, BOOL bFail)
{
if ( IsOpen() )
Close();
SetStatus("Opening " + m_DefaultSQL + " ...");
m_strFilter = Filter;
m_strSort = Sort;
Open();
// Throw exception if record not found
if ( bFail && IsEOF() )
THROW(new CMyException(m_DefaultSQL + " record not found!"));
return !IsEOF();
}
template<class T, int M> void CDataSet<T, M>::LoadAll(CArray<T, T>& A, int N)
{
SetStatus("Loading " + m_DefaultSQL + " ...");
A.RemoveAll();
while ( !IsEOF() && (N == 0 || A.GetSize() < N) )
{
A.Add(m_Data);
MoveNext();
}
}
template<class T, int M> void CDataSet<T, M>::SaveAll(CArray<T, T>& A)
{
SetStatus("Writing " + m_DefaultSQL + " ...");
for ( int i = 0;i < A.GetSize(); i++ )
{
AddNew();
Store(A[i]);
}
}
template<class T, int M> void CDataSet<T, M>::Store(T& Data)
{
SetStatus("Updating " + m_DefaultSQL + " ...");
Edit();
m_Data = Data;
Update();
}
template<class T, int M> void CDataSet<T, M>::Close()
{
CRecordset::Close();
SetStatus("Ready");
}
CDataSet類中許多方法都使用SetStatus()函數,它的作用是顯示一個沙漏以及在應用程序狀 態條顯示當前的操作狀態。
void SetStatus(const CString Msg)
{
CFrameWnd* pMainFrame = (CFrameWnd*)AfxGetMainWnd();
if ( pMainFrame )
{
pMainFrame->SetMessageText(Msg);
pMainFrame->UpdateWindow();
if ( strcmp(Msg, "Ready") == 0 )
pMainFrame->EndWaitCursor();
else
pMainFrame->BeginWaitCursor();
}
}
當處理大量數據和執行復雜的查詢時,這個函數特別有用。它告訴用戶應用程序正在處理數據。
CMyException是一個簡單的異常處理類,如果運行出錯,通過這個類來丟出異常,並顯示指定的錯誤信息。以下使這個類的定義和實現:
class CMyException: public CException {
CString m_ErrorMsg;
public:
CMyException(int ErrMsgResourceID);
CMyException(CString ErrMsg);
BOOL GetErrorMessage(LPTSTR lpszError,?
UINT nMaxError,
PUINT pnHelpContext = NULL);
};
CMyException::CMyException(int ResourceID)
{
m_ErrorMsg.LoadString(ResourceID);
}
CMyException::CMyException(CString ErrorMsg)
{
m_ErrorMsg = ErrorMsg;
}
BOOL CMyException::GetErrorMessage(LPTSTR lpszError,
UINT nMaxError,
PUINT)
{
strncpy(lpszError, (LPCSTR)m_ErrorMsg, nMaxError);
return TRUE;
}
在使用CDataSet類的應用程序中,對象可以被實例化如下:
CDataset<DataObj, 4> MySet("Table1", &db);
這裡“Table1”是數據庫表的名字,“&db”指向一個打開的數據庫,在大多數情況下(尤其是當使用事務處理時),實例創建數據庫對象並且在打開記錄機之前從外部打開數據庫。您是否注意到在Listing 2 的代碼中有一件事情沒有做?,對每一個數據類而言必須要單獨實現DoFieldExchange()來建立一個數據對象成員和數據庫字段之間的聯接,Listing 3 中的代碼告訴您如何為DataObj實現DoFieldExchange()。 數據對象的 DoFieldExchange() 方法
void CDataSet<DataObj, 4>::DoFieldExchange(CFieldExchange* pFX)
{
pFX->SetFieldType(CFieldExchange::outputColumn);
RFX_Text(pFX, "VAR1", m_Data.Var1);
RFX_Text(pFX, "VAR2", m_Data.Var2);
RFX_Int(pFX, "VAR3", m_Data.Var3);
RFX_Single(pFX, "VAR4", m_Data.Var4);
}
以下是使用CDataSet的一個例子:
CDatabase db;
CArray<DataObj, DataObj> A;
TRY {
db.Open("TEST");
// Create recordset object
CDataSet<DataObj, 4> MySet("Table1", &db);
// Set default filter to var1
MySet.m_DefaultFilter = "Var1 = ''%s''";
MySet.Open();
// Load all of the records
MySet.LoadAll(A);
// Find some record
if ( MySet.Search("Anything") )
{
DataObj B;
// Load, update, and store new record
MySet.Load(B);
B.Var1 = "Anything else?";
MySet.Store(B);
}
MySet.Close();
AfxMessageBox("Data is loaded!");
}
CATCH_ALL(e) {
e->ReportError();
}
END_CATCH_ALL
為了測試例子,要創建一個系統數據源“Test”(MS Access Driver),指向Test.mdb,運行DbTest.exe,在菜單中選擇 Test =〉DataSet。