因为MFC完全支持数据库应用程序的开发,所以大多数数据库应用都使用CDatabase和CRecordset类,并且类向导(Class Wizard)提供了快速简易的方式来使用这两个类。有一点不足的就是当应用程序涉及到多表数据库时,类向导将产生大量的关于记录集的源码文件使得工程给人的感觉很臃肿混乱。
本文介绍如何使用一个模板记录集类来降低类向导所产生的记录集文件的数量,同时增强记录集类(CRecordset)的功能。这个模板记录集类叫做:CDataSet。它的主要目的是降低代码量,为数据对象数组提供一个接口。
CDataSet类定义如下:
01.
CDataSet 头文件
02.
template
class
CDataSet :
public
CRecordset
03.
{
04.
public
:
05.
CDataSet(
LPCSTR
Table, CDatabase* pdb);
06.
T m_Data;
// Attached object
07.
CString m_DefaultSQL;
// Default SQL SELECT statement
08.
CString m_DefaultSort;
// Default SQL ORDER BY clause
09.
CString m_DefaultFilter;
// Default SQL WHERE clause
10.
11.
// Operations
12.
public
:
13.
virtual
BOOL
Search(
LPCSTR
Filter,
LPCSTR
Sort = NULL,
BOOL
bFail = FALSE);
14.
virtual
BOOL
DirectSearch(
LPCSTR
Filter,
LPCSTR
Sort = NULL,
BOOL
bFail = FALSE);
15.
virtual
void
LoadAll(CArray& A,
int
N = 0);
16.
virtual
void
SaveAll(CArray& A);
17.
virtual
void
Load(T& Data) { Data = m_Data; }
18.
virtual
void
Store(T& Data);
19.
20.
// Implementation
21.
protected
:
22.
virtual
CString GetDefaultSQL() {
return
m_DefaultSQL; }
23.
virtual
void
DoFieldExchange(CFieldExchange* pFX);
24.
};
这个模板有两个参数:一个是数据对象类,另一个是绑定字段的数量。数据对象类由一组成员变量组成,对应着不同的数据库表的字段,例如:
01.
struct
DataObj {
02.
CString Var1;
03.
CString Var2;
04.
int
Var3;
05.
float
Var4;
06.
07.
// ......
08.
09.
// 成员函数
10.
};
如果将数据对象定义成一个类,而不是一个结构,就必须定义缺省的供类模板使用的构造函数并重载 “=”操作符(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实现
01.
template
CDataSet::CDataSet(
LPCSTR
Table, CDatabase* pdb) :
02.
CRecordset(pdb)
03.
{
04.
m_nFields = M;
05.
m_DefaultSQL = Table;
06.
m_DefaultFilter =
"%s"
;
07.
}
08.
09.
template
BOOL
CDataSet::Search(
LPCSTR
Filter,
LPCSTR
Sort,
BOOL
bFail)
10.
{
11.
if
( IsOpen() )
12.
Close();
13.
14.
SetStatus(
"Opening "
+ m_DefaultSQL +
" ..."
);
15.
16.
if
( Filter )
17.
m_strFilter.Format(m_DefaultFilter, Filter);
18.
else
19.
m_strFilter =
""
;
20.
m_strSort = Sort;
21.
22.
Open();
23.
24.
// Throw exception if record not found
25.
if
( bFail && IsEOF() )
26.
THROW(
new
CMyException(m_DefaultSQL +
" record not found!"
));
27.
return
!IsEOF();
28.
}
29.
30.
template
BOOL
CDataSet::DirectSearch(
LPCSTR
Filter,
LPCSTR
Sort,
BOOL
bFail)
31.
{
32.
if
( IsOpen() )
33.
Close();
34.
35.
SetStatus(
"Opening "
+ m_DefaultSQL +
" ..."
);
36.
37.
m_strFilter = Filter;
38.
m_strSort = Sort;
39.
Open();
40.
41.
// Throw exception if record not found
42.
if
( bFail && IsEOF() )
43.
THROW(
new
CMyException(m_DefaultSQL +
" record not found!"
));
44.
return
!IsEOF();
45.
}
46.
47.
template
void
CDataSet::LoadAll(CArray& A,
int
N)
48.
{
49.
SetStatus(
"Loading "
+ m_DefaultSQL +
" ..."
);
50.
A.RemoveAll();
51.
52.
while
( !IsEOF() && (N == 0 || A.GetSize() < N) )
53.
{
54.
A.Add(m_Data);
55.
MoveNext();
56.
}
57.
}
58.
59.
template
void
CDataSet::SaveAll(CArray& A)
60.
{
61.
SetStatus(
"Writing "
+ m_DefaultSQL +
" ..."
);
62.
63.
for
(
int
i = 0;i < A.GetSize(); i++ )
64.
{
65.
AddNew();
66.
Store(A[i]);
67.
}
68.
}
69.
70.
template
void
CDataSet::Store(T& Data)
71.
{
72.
SetStatus(
"Updating "
+ m_DefaultSQL +
" ..."
);
73.
Edit();
74.
m_Data = Data;
75.
Update();
76.
}
77.
78.
template
void
CDataSet::Close()
79.
{
80.
CRecordset::Close();
81.
SetStatus(
"Ready"
);
82.
}
CDataSet类中许多方法都使用SetStatus()函数,它的作用是显示一个沙漏以及在应用程序状态条显示当前的操作状态。
01.
void
SetStatus(
const
CString Msg)
02.
{
03.
CFrameWnd* pMainFrame = (CFrameWnd*)AfxGetMainWnd();
04.
05.
if
( pMainFrame )
06.
{
07.
pMainFrame->SetMessageText(Msg);
08.
pMainFrame->UpdateWindow();
09.
10.
if
(
strcmp
(Msg,
"Ready"
) == 0 )
11.
pMainFrame->EndWaitCursor();
12.
else
13.
pMainFrame->BeginWaitCursor();
14.
}
15.
}
当处理大量数据和执行复杂的查询时,这个函数特别有用。它告诉用户应用程序正在处理数据.
CMyException是一个简单的异常处理类,如果运行出错,通过这个类来丢出异常,并显示指定的错误信息。
以下使这个类的定义和实现:
01.
class
CMyException:
public
CException {
02.
CString m_ErrorMsg;
03.
public
:
04.
CMyException(
int
ErrMsgResourceID);
05.
CMyException(CString ErrMsg);
06.
BOOL
GetErrorMessage(
LPTSTR
lpszError,?
07.
UINT
nMaxError,
08.
PUINT
pnHelpContext = NULL);
09.
};
10.
11.
CMyException::CMyException(
int
ResourceID)
12.
{
13.
m_ErrorMsg.LoadString(ResourceID);
14.
}
15.
16.
CMyException::CMyException(CString ErrorMsg)
17.
{
18.
m_ErrorMsg = ErrorMsg;
19.
}
20.
21.
BOOL
CMyException::GetErrorMessage(
LPTSTR
lpszError,
22.
UINT
nMaxError,
23.
PUINT
)
24.
{
25.
strncpy
(lpszError, (
LPCSTR
)m_ErrorMsg, nMaxError);
26.
return
TRUE;
27.
}
在使用CDataSet类的应用程序中,对象可以被实例化如下:
1.
CDataset MySet(
"Table1"
, &db);
这里“Table1”是数据库表的名字,“&db”指向一个打开的数据库,在大多数情况下(尤其是当使用事务处理时),实例创建数据库对象并且在打开记录机之前从外部打开数据库。
您是否注意到在Listing 2 的代码中有一件事情没有做?,对每一个数据类而言必须要单独实现DoFieldExchange()来建立一个数据对象成员和数据库字段之间的联接,Listing 3 中的代码告诉您如何为DataObj实现DoFieldExchange()。
数据对象的 DoFieldExchange() 方法
1.
void
CDataSet::DoFieldExchange(CFieldExchange* pFX)
2.
{
3.
pFX->SetFieldType(CFieldExchange::outputColumn);
4.
RFX_Text(pFX,
"VAR1"
, m_Data.Var1);
5.
RFX_Text(pFX,
"VAR2"
, m_Data.Var2);
6.
RFX_Int(pFX,
"VAR3"
, m_Data.Var3);
7.
RFX_Single(pFX,
"VAR4"
, m_Data.Var4);
8.
}
以下是使用CDataSet的一个例子:
01.
CDatabase db;
02.
CArray A;
03.
04.
TRY {
05.
db.Open(
"TEST"
);
06.
07.
// Create recordset object
08.
CDataSet MySet(
"Table1"
, &db);
09.
10.
// Set default filter to var1
11.
MySet.m_DefaultFilter =
"Var1 = ''%s''"
;
12.
MySet.Open();
13.
14.
// Load all of the records
15.
MySet.LoadAll(A);
16.
17.
// Find some record
18.
if
( MySet.Search(
"Anything"
) )
19.
{
20.
DataObj B;
21.
22.
// Load, update, and store new record
23.
MySet.Load(B);
24.
B.Var1 =
"Anything else?"
;
25.
MySet.Store(B);
26.
}
27.
28.
MySet.Close();
29.
AfxMessageBox(
"Data is loaded!"
);
30.
}
31.
CATCH_ALL(e) {
32.
e->ReportError();
33.
}
34.
END_CATCH_ALL
为了测试例子,要创建一个系统数据源“Test”(MS Access Driver),指向Test.mdb,运行DbTest.exe,在菜单中选择 Test =〉DataSet。