CRecordset类的介绍和使用

 
CRecordset 类代表一个记录集.该类是 MFC ODBC 类中最重要、功能最强大的类。
10.5.1 动态集、快照、光标和光标库
在多任务操作系统或网络环境中,多个用户可以共享同一个数据源。共享数据的一个主要问题是如何协调各个用户对数据源的修改。例如,当某一个应用改变了数据源中的记录时,别的连接至该数据源的应用应该如何处理。对于这个问题,基于 MFC ODBC 应用程序可以采取几种不同的处理办法,这将由程序采用哪种记录集决定。
记录集主要分为快照 (Snapshot) 和动态集 (Dynaset) 两种, CRecordset 类对这两者都支持。这两种记录集的不同表现在它们对别的应用改变数据源记录采取了不同的处理方法。
快照型记录集提供了对数据的静态视.快照是个很形象的术语,就好象对数据源的某些记录照了一张照片一样.当别的用户改变了记录时(包括修改、添加和删除),快照中的记录不受影响,也就是说,快照不反映别的用户对数据源记录的改变.直到调用了 CRecordset::Requery 重新查询后,快照才会反映变化.对于象产生报告或执行计算这样的不希望中途变动的工作,快照是很有用的。需要指出的是,快照的这种静态特性是相对于别的用户而言的,它会正确反映由本身用户对记录的修改和删除,但对于新添加的记录直到调用 Requery 后才能反映到快照中.
动态集提供了数据的动态视.当别的用户修改或删除了记录集中的记录时,会在动态集中反映出来:当滚动到修改过的记录时对其所作的修改会立即反映到动态集中,当记录被删除时, MFC 代码会跳过记录集中的删除部分.对于其它用户添加的记录,直到调用 Requery 时,才会在动态集中反映出来。本身应用程序对记录的修改、添加和删除会反映在动态集中。当数据必须是动态的时侯,使用动态集是最适合的。例如,在一个火车票联网售票系统中,显然应该用动态集随时反映出共享数据的变化。
在记录集中滚动,需要有一个标志来指明滚动后的位置(当前位置)。 ODBC 驱动程序会维护一个光标,用来跟踪记录集的当前记录,可以把光标理解成跟踪记录集位置的一种机制。
光标库 (Cursor Library) 是处于 ODBC 驱动程序管理器和驱动程序之间的动态链接库 (ODBCCR32.DLL) .光标库的主要功能是支持快照以及为底层驱动程序提供双向滚动能力,高层次的驱动程序不需要光标库,因为它们是可滚动的.光标库管理快照记录的缓冲区,该缓冲区反映本程序对记录的修改和删除,但不反映其它用户对记录的改变,由此可见,快照实际上相当于当前的光标库缓冲区.
应注意的是,快照是一种静态光标 (Static Cursor) .静态光标直到滚动到某个记录才能取得该记录的数据.因此,要保证所有的记录都被快照,可以先滚动到记录集的末尾,然后再滚动到感兴趣的第一个记录上.这样做的缺点是滚动到末尾需要额外的开销,会降低性能.
与快照不同,动态集不用光标库维持的缓冲区来存放记录.实际上,动态集是不使用光标库的,因为光标库会屏蔽掉一些支持动态集的底层驱动程序功能.动态集是一种键集驱动光标 (Keyset-Driven Cursor) ,当打开一个动态集时,驱动程序保存记录集中每个记录的键.只要光标在动态集中滚动,驱动程序就会通过键来从数据源中检取当前记录,从而保证选取的记录与数据源同步.
从上面的分析中可以看出,快照和动态集有一个共同的特点,那就是在建立记录集后,记录集中的成员就已经确定了.这就是为什么两种记录集都不能反映别的用户添加记录的原因.
10.5.2 域数据成员与数据交换
CRecordset 类代表一个记录集.用户一般需要用 ClassWizard 创建一个 CRecordset 的派生类. ClassWizard 可以为派生的记录集类创建一批数据成员,这些数据成员与记录的各字段相对应,被称为字段数据成员或域数据成员.例如,对于表 10.2 所示的将在后面例子中使用的数据库表, ClassWizard 会在派生类中加入 6 个域数据成员,如清单 10.1 所示.可以看出域数据成员与表中的字段名字类似,且类型匹配.
 
10.2 stdreg32.mdb 中的 Section
CourseID
(Text)
SectionNo
(Text)
InstructorID
(Text)
RoomNo
(Text)
Schedule
(Text)
Capacity
(int)
MATH101
1
KLAUSENJ
KEN-12
MWF10-11
40
MATH101
2
ROGERSN
WIL-1088
TTH3:30-5
15
MATH201
1
ROGERSN
WIL-1034
MWF2-3
20
MATH201
2
SMITHJ
WIL-1054
MWF3-4
25
MATH202
1
KLA
WIL-1054
MWF9-10
20
MATH202
2
ROGERSN
KEN-12
TTH9:30-11
15
MATH202
3
KLAUSENJ
WIL-2033
TTH3-4:30
15

 
清单 10.1 派生类中的域数据成员
class CSectionSet : public CRecordset
{
public:
. . . . . .
//{{AFX_FIELD(CSectionSet, CRecordset)
CString m_CourseID;
CString m_SectionNo;
CString m_InstructorID;
CString m_RoomNo;
CString m_Schedule;
int m_Capacity;
//}}AFX_FIELD
. . . . . .
};
 
域数据成员用来保存某条记录的各个字段,它们是程序与记录之间的缓冲区.域数据成员代表当前记录,当在记录集中滚动到某一记录时,框架自动地把记录的各个字段拷贝到记录集对象的域数据成员中.当用户要修改当前记录或增加新记录时,程序先将各字段的新值放入域数据成员中,然后调用相应的 CRecordset 成员函数把域数据成员设置到数据源中.
不难看出,在记录集与数据源之间有一个数据交换问题. CRecordset 类使用"记录域交换" (Record Field Exchange ,缩写为 RFX) 机制自动地在域数据成员和数据源之间交换数据. RFX 机制与对话数据交换 (DDX) 类似. CRecordset 的成员函数 DoFieldExchange 负责数据交换任务,在该函数中调用了一系列 RFX 函数.当用户用 ClassWizard 加入域数据成员时, ClassWizard 会自动在 DoFieldExchange 中建立 RFX .典型 DoFieldExchange 如清单 10.2 所示:
 
清单 10.2 典型的 DoFieldExchange 函数
void CSectionSet::DoFieldExchange(CFieldExchange* pFX)
{
//{{AFX_FIELD_MAP(CSectionSet)
pFX->SetFieldType(CFieldExchange::outputColumn);
RFX_Text(pFX, _T("[CourseID]"), m_CourseID);
RFX_Text(pFX, _T("[SectionNo]"), m_SectionNo);
RFX_Text(pFX, _T("[InstructorID]"), m_InstructorID);
RFX_Text(pFX, _T("[RoomNo]"), m_RoomNo);
RFX_Text(pFX, _T("[Schedule]"), m_Schedule);
RFX_Int(pFX, _T("[Capacity]"), m_Capacity);
//}}AFX_FIELD_MAP
}
 
 
10.5.3 SQL 查询
记录集的建立实际上主要是一个查询过程, SQL SELECT 语句用来查询数据源.在建立记录集时, CRecordset 会根据一些参数构造一个 SELECT 语句来查询数据源,并用查询的结果创建记录集.明白这一点对理解 CRecordset 至关重要. SELECT 语句的句法如下:
SELECT rfx-field-list FROM table-name [WHERE m_strFilter]
[ORDER BY m_strSort]
其中 table-name 是表名, rfx-field-list 是选择的列(字段). WHERE ORDER BY 是两个子句,分别用来过滤和排序。下面是 SELECT 语句的一些例子:
SELECT CourseID, InstructorID FROM Section
SELECT * FROM Section WHERE CourseID=‘MATH202’ AND Capacity=15
SELECT InstructorID FROM Section ORDER BY CourseID ASC
其中第一个语句从 Section 表中选择 CourseID InstructorID 字段.第二个语句从 Section 表中选择 CourseID MATH202 Capacity 等于 15 的记录,在该语句中使用了象" AND "或" OR "这样的逻辑连接符.要注意在 SQL 语句中引用字符串、日期或时间等类型的数据时要用单引号括起来,而数值型数据则不用.第三个语句从 Section 表中选择 InstructorID 列并且按 CourseID 的升序排列,若要降序排列,可使用关键字 DESC
提示:如果列名或表名中包含有空格,则必需用方括号把该名称包起来。例如,如果有一列名为 “Client Name” ,则应该写成 “[Client Name]”
 
10.5.4 记录集的建立和关闭
要建立记录集,首先要构造一个 CRecordset 派生类对象,然后调用 Open 成员函数查询数据源中的记录并建立记录集.在 Open 函数中,可能会调用 GetDefaultConnect GetDefaultSQL 函数.函数的声明为
CRecordset( CDatabase* pDatabase = NULL);
参数 pDatabase 指向一个 CDatabase 对象,用来获取数据源.如果 pDatabase NULL ,则会在 Open 函数中自动构建一个 CDatabase 对象.如果 CDatabase 对象还未与数据源连接,那么在 Open 函数中会建立连接,连接字符串(参见 10.3.1 )由成员函数 GetDefaultConnect 提供.
virtual CString GetDefaultConnect( );
该函数返回缺省的连接字符串. Open 函数在必要的时侯会调用该函数获取连接字符串以建立与数据源的连接.一般需要在 CRecordset 派生类中覆盖该函数并在新版的函数中提供连接字符串.
virtual BOOL Open( UINT nOpenType = AFX_DB_USE_DEFAULT_TYPE, LPCTSTR lpszSQL = NULL, DWORD dwOptions = none );
throw( CDBException, CMemoryException );
该函数使用指定的 SQL 语句查询数据源中的记录并按指定的类型和选项建立记录集.参数 nOpenType 说明了记录集的类型,如表 10.3 所示,如果要求的类型驱动程序不支持,则函数将产生一个异常.参数 lpszSQL 是一个 SQL SELECT 语句,或是一个表名.函数用 lpszSQL 来进行查询,如果该参数为 NULL ,则函数会调用 GetDefaultSQL 获取缺省的 SQL 语句.参数 dwOptions 可以是一些选项的组合,常用的选项在表 10.4 中列出.若创建成功则函数返回 TRUE ,若函数调用了 CDatabase::Open 且返回 FALSE ,则函数返回 FALSE
10.3 记录集的类型
类型
含义
AFX_DB_USE_DEFAULT_TYPE
使用缺省值.
CRecordset::dynaset
可双向滚动的动态集.
CRecordset::snapshot
可双向滚动的快照.
CRecordset::dynamic
提供比动态集更好的动态特性,大部分 ODBC 驱动程序不支持这种记录集.
ODBC 驱动程序不支持这种记录集.
CRecordset::forwardOnly
只能前向滚动的只读记录集.
 
 
10.4 创建记录集时的常用选项
 
10.4 创建记录集时的常用选项
10.4 创建记录集时的常用选项
选项
含义
CRecordset::none
无选项(缺省).
CRecordset::appendOnly
不允许修改和删除记录,但可以添加记录.
CRecordset::readOnly
记录集是只读的.
CRecordset::skipDeletedRecords
有些数据库(如 FoxPro )在删除记录时并不真删除,而是做个删除标记,在滚动时将跳过这些被删除的记录.
FoxPro )在删除记录时并不真删除,而是做个删除标记,在滚动时将跳过这些被删除的记录.
virtual CString GetDefaultSQL( );
Open
函数在必要时会调用该函数返回缺省的 SQL 语句或表名以查询数据源中的记录.一般需要在 CRecordset 派生类中覆盖该函数并在新版的函数中提供 SQL 语句或表名.下面是一些返回字符串的例子.
“Section” //
选择 Section 表中的所有记录到记录集中
“Section, Course” //
合并 Section 表和 Course 表的各列到记录集中
// Section 表中的所有记录按 CourseID 的升序进行排序,然后建立记录集
“SELECT * FROM Section ORDER BY CourseID ASC”
上面的例子说明,通过合理地安排 SQL 语句和表名, Open 函数可以十分灵活地查询数据源中的记录.用户可以合并多个表的字段,也可以只选择记录中的某些字段,还可以对记录进行过滤和排序.
上一小节说过,在建立记录集时, CRecordset 会构造一个 SELECT 语句来查询数据源.如果在调用 Open 时只提供了表名,那么 SELECT 语句还缺少选择列参数 rfx-field-list (参见 10.5.3 ).框架规定,如果只提供了表名,则选择列的信息从 DoFieldExchange 中的 RFX 语句里提取.例如,如果在调用 Open 时只提供了" Section "表名,那么将会构造如下一个 SELECT 语句:
SELECT CourseID,SectionNo,InstructorID,RoomNo, Schedule,Capacity FROM Section
 
建立记录集后,用户可以随时调用 Requery 成员函数来重新查询和建立记录集. Requery 有两个重要用途:
使记录集能反映用户对数据源的改变(参见 10.5.1 ).
按照新的过滤或排序方法查询记录并重新建立记录集.
 
在调用 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 对象时记录集也随之关闭。
 
10.5.5 滚动记录
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 不行 )
 
10.5.6 修改、添加和删除记录
要修改当前记录,应该按下列步骤进行:
调用 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 则表明可以添加记录.
 
 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值