函数调用vc++笔记----CRecordset类

之前一直在查找函数调用之类的问题,现在正好有机会和大家分享一下.

    CRecordset类代表一个记载集.该类是MFC的ODBC类中最重要、功能最强大的类。

     动态集、快照、光标和光标库

    在多任务操纵系统或网络环境中,多个用户可以共享同一个数据源。共享数据的一个重要问题是如何协调各个用户对数据源的修改。例如,当某一个应用转变了数据源中的记载时,别的连接至该数据源的应用应当如何处置。对于这个问题,基于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驱动程序不支撑这类记载集.

CRecordset::forwardOnly

只能前向滚动的只读记载集.

    表10.4 创建记载集时的经常使用选项

    

选项

含义

CRecordset::none

无选项(缺省).

CRecordset::appendOnly

不允许修改和删除记载,但可以添加记载.

CRecordset::readOnly

记载集是只读的.

CRecordset::skipDeletedRecords

有些数据库(如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则表明可以添加记载.

    

    

文章结束给大家分享下程序员的一些笑话语录: 一条狗在街上闲逛,看见橱窗里一张告示:「招聘程序员。会编程,有团队精神,至少精通两种语言。均等机会。」
  那条狗就进去申请,但是被拒绝了。
  「我不能雇一条狗在公司里做事。」经理说。
  狗不服气,指着告示上「均等机会」几字抗议。
  经理没法,叹了口气,不屑地问道:「你会编程吗?」
  那条狗默默地走到电脑前,编了个程序,运作准确。
  「你有团队精神吗?」经理问。
  那条狗掉头看了看门外,一大群野狗在外面虎视耽耽。
  「我真的不能雇狗做这份工作。」经理气急败坏地说。
  「就算会编程、有团队精神,但是我需要的雇员至少要能精通两种语言。」
  那条狗抬头看着经理说:「喵-噢。」

--------------------------------- 原创文章 By
函数和调用
---------------------------------

posted @ 2013-06-01 21:17  xinyuyuanm  阅读(2091)  评论(0编辑  收藏  举报