|
10.5 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表
清单10.1 派生类中的域数据成员
域数据成员用来保存某条记录的各个字段,它们是程序与记录之间的缓冲区.域数据成员代表当前记录,当在记录集中滚动到某一记录时,框架自动地把记录的各个字段拷贝到记录集对象的域数据成员中.当用户要修改当前记录或增加新记录时,程序先将各字段的新值放入域数据成员中,然后调用相应的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语句的一些例子:
其中第一个语句从Section表中选择CourseID和InstructorID字段.第二个语句从Section表中选择CourseID为MATH202且Capacity等于15的记录,在该语句中使用了象"AND"或"OR"这样的逻辑连接符.要注意在SQL语句中引用字符串、日期或时间等类型的数据时要用单引号括起来,而数值型数据则不用.第三个语句从Section表中选择InstructorID列并且按CourseID的升序排列,若要降序排列,可使用关键字DESC.
10.5.4 记录集的建立和关闭 要建立记录集,首先要构造一个CRecordset派生类对象,然后调用Open成员函数查询数据源中的记录并建立记录集.在Open函数中,可能会调用GetDefaultConnect和GetDefaultSQL函数.函数的声明为
表10.3 记录集的类型
表10.4 创建记录集时的常用选项
上面的例子说明,通过合理地安排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有两个重要用途:
在调用Requery之前,可调用CanRestart来判断记录集是否支持Requery操作.要记住Requery只能在成功调用Open后调用,所以程序应调用IsOpen来判断记录集是否已建立.函数的声明为
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提供了几个成员函数用来在记录集中滚动,如下所示.当用这些函数滚动到一个新记录时,框架会自动地把新记录的内容拷贝到域数据成员中.
如果在建立记录集时选择了CRecordset::skipDeletedRecords选项,那么除了SetAbsolutePosition外,在滚动记录时将跳过被删除的记录,这一点对象FoxPro这样的数据库十分重要. 如果记录集是空的,那么调用上述函数将产生异常.另外,必须保证滚动没有超出记录集的边界.调用IsEOF和IsBOF可以进行这方面的检测.
下面是一个使用IsEOF的例子: while(!m_pSet->IsEOF( )) m_pSet->MoveNext( ); 调用GetRecordCound可获得记录集中的记录总数,该函数的声明为
10.5.6 修改、添加和删除记录 要修改当前记录,应该按下列步骤进行:
要向记录集中添加新的记录,应该按下列步骤进行:
如果记录集是快照,那么在添加一个新的记录后,需要调用Requery重新查询,因为快照无法反映添加操作. 要删除记录集的当前记录,应按下面两步进行:
上面提到的函数声明为:
在对记录集进行更改以前,程序也许要调用下列函数来判断记录集是否是可以更改的,因为如果在不能更改的记录集中进行修改、添加或删除将导致异常的产生.
本教程幼髡? 作者: 不详, 来源: Visual C++王朝 |