加载中…
正文 字体大小:

MFC ODBC 极品笔记(2) 连接 查询 插入 删除 事务 该有的都有了

(2011-01-18 21:31:34)
标签:

it

分类: Game_MFC_windows

遍历记录集合

CRecordset类中有一组函数负责记录集指针的移动,例如使用记录集指针下移一个记录、使用记录集指针上移一个记录等。

1、 MoveFirst()函数:使指针移动到第一条记录

2、 MoveLast()函数:使指针移动到最后一条记录

3、 MoveNext()函数:使指针移动到下一条记录

4、 MovePrev()函数:使指针移动到前一条记录

5、 IsBOF()函数:当指针移动到第一条记录前面或者表中没有记录的时候返回真

6、 IsEOF()函数:当指针移动到最后一条记录后面的时候返回真

知道上面的这些函数的意义后,下面来看如何遍历记录集。下面的代码中m_pSet是一个记录集指针,m_list为一个列表框控件(ClistBox类)的变量:

if(!m_pSet->IsOpen()) //IsOpen函数检测记录集是否打开

m_pSet->Open();

m_pSet->MoveFirst();

while(!m_pSet->IsEOF())

{

m_list.AddString(m_pSet->m_name);

m_pSet->MoveNext();

}

m_pSet->MoveFirst(); //遍历完成后,使记录集指针指向第一条记录

书签定位和绝对定位

当在记录集中浏览的时候,可能想返回记录集中特定的一条记录,CRecordset提供了两种方法可以指定记录集到特定的位置。

1、 书签定位

可以在记录集中的某一条记录增加一个书签。在记录集浏览时由于用户的增删操作使记录的绝对位置发生改变,所以以来绝对位置是不可靠的。因此需要使用书签定位来为所想要的记录定位。CRecordset类中提供的书签定位的方法是GetBookmarkSetBookmark两个函数,他们的原型如下:

void GetBookmark(CDBVariant& varBookmark); //参数为CDBVariant的对象

void SetBookmark(const CDBVariant& varBookmark);

这里只需直接使用CDBVariant的对象即可。

//创建CDBVariant对象

CDBVariant bookmark;

//rsCRecordset类或CRecordset类派生类的对象

rs.GetBookmark(bookmark);

//一系列移动到其他记录的代码

rs.MoveNext();

rs.MoveNext();

rs.SetBookmark(bookmark);

GetBookmark函数将当前的记录存入一个CDBVariant的对象中,经过一系列的纪录移动之后,在调用SetBookmark,并且用刚才记录“书签”的CDBVariant对象bookmark作参数来使用当前记录集重新指向“书签”的位置。

是否支持书签定位取决于ODBC驱动程序和记录集类型。可以通过调用CRecordset::CanBookmark来确定是否支持书签定位。如果想支持书签定位,还需要在记录集的Open函数的dwOptions参数位置中加入CRecordset::useBookmarks参数。注意forward-only recordsets(只向前)类型的记录集也不支持书签定位。还有一点,就是在某些记录集操作之后,也应该及时检查前面所设置的“书签”是否还可以继续使用。例如,对一个记录及进行了Requery操作之后,书签就可能不再有效了。所以,在调用SetBookmark函数之前,应该先调用CDatabase::GetBookmarkPersistence函数来核对是否可以安全的调用SetBookmark函数。下面是该函数原型:DWORD GetBookmarkPersistence() const;

这个函数的返回值为bitmask,这是一个DWORD类型的返回值。该值可以是下表中的多个bitmask值的组合。

bitmask

书签的有效性

SQL_BP_CLOSE

Requery操作后,书签有效

SQL_BP_DELETE

对某行执行delete操作后,书签对此行依然有效

SQL_BP_DROP

一次Close操作后,书签有效

SQL_BP_SCROLL

任何Move操作之后,书签都有效

SQL_BP_TRANSACTION

一次事务被提交或回滚后,书签有效

SQL_BP_UPDATE

对某行执行Update操作后,书签对此行有效

SQL_BP_OTHER_HSTMT

与某记录集对象相关的书签对另一记录集也有效

2、 绝对定位

相对于书签定位,绝对定位就好像记住某本书的某一个固定页码一样。绝对定位就是通过原始的记录位置来设置当前记录,比如可以设置记录集中第8条记录为当前记录。如想使用绝对定位来改变当前的记录集位置,可以调用CRecordset::SetAbsolutePosition函数。其原型为:

void SetAbsolutePosition(long nRows);

nRows表示记录集中的一个绝对位置。调用该函数会把记录集指针定位到nRows参数所指行号的记录上。

下面的代码表示把记录集定位到第12条记录的位置上:

long row;

row 12;

rs.SetAbsolutePositon(row);

对于ODBC的记录集来说,绝对位置1指的是记录集当中的第一条记录,绝对位置如果是0则代表的是BOF位置(在第一条记录之前)。

***forward-only recordsets(只向前)类型的记录集不支持SetAbsolutePosition方法。此外,记录的绝对位置存在潜在的不可靠性。如果用户删除了某一条记录,那么后续记录的位置都发上变化,并且记录集也可能被再次重新创建,不能确保某条记录在创建的记录集中有与原来相同位置,因此建议使用书签定位。 ***

获取记录集的数据

通过在对话框上添加控件,并且为控件绑定变量来达到数据交换并显示的目的,与控件绑定的变量正是记录集中的那些字段变量。步骤如下:

按照所需显示的字段数量,在对话框窗体上添加几个编辑框控件,将控件的ID改成与表中字段相似的名称

再按住Ctrl的同时双击每一个控件,为它们增加成员变量

Add Member Variable对话框中单击“Member variable name”下拉框,为每个控件一次选择记录集中的各个变量。

到底记录集中的变量是如何把数据显示在控件上呢?下面讲述DDX技术。

DDXDialog Data Exchange的缩写,即对话框数据交换。它在对话框的控件与记录集的变量之间建立起一座桥梁,可以使他们双向交换数据。

CRecordView类里面有一个DoDataExchange函数,所有的DDX函数都是在DoDataExchange中调用的。DoDataExchange函数为DDX函数提供了一个CDataExchange类对象的指针。在工程刚刚创建时,CSampleView::DodataExchange函数是一个空函数,因此此时对话框上并没有控件,更没有与控件对应的变量。但经过窗体添加控件、为控件添加变量之后,再打开DoDataExchange函数,将会出现下面这样的代码:

void CSampleView::DoDataExchange(CDataExchange* pDX)

{

CRecordView::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CSampleView)

DDX_FieldText(pDX, IDC_ID, m_pSet->m_id, m_pSet);

DDX_FieldText(pDX, IDC_NAME, m_pSet->m_name, m_pSet);

DDX_FieldText(pDX, IDC_DEPARTMENT, m_pSet->m_department, m_pSet);

DDX_FieldText(pDX, IDC_AGE, m_pSet->m_age, m_pSet);

DDX_FieldText(pDX, IDC_COMMENT, m_pSet->m_comment, m_pSet);

//}}AFX_DATA_MAP

}

DDX_FieldText函数负责在对话框控件和记录集中字段变量之间建立联系。因为记录集中的字段变量对应的是数据库中表的每个字段,所以就能在控件上看到表中的数据了。DDX函数有四个参数,分别为:

一个指向CDateExchange类对象的指针

对话框上控件的ID

记录集中要与对象绑定的字段变量

记录集对象的指针

DDX可以管理以下类型数据变量与控件的数据交换。它们是shortlongintDWORDCStringfloatdoubleBOOL以及BYTE

程序通过UpdateData这个函数在控件与变量之间达到双向数据,而不是直接调用DoDataExchangeUpdateData函数的原型如下:

BOOL UpdateData(BOOL bSaveAndValidate TRUE);

bSaveAndValidate指示了数据传输的方向,当为TRUE时就是控件向变量传输。反之,就是变量向控件传输数据。默认值为TRUE

添加记录

对记录的操作大多数都是由CRecordset类来负责的,执行添加的任务也不例外。CRecordset类中的函数AddNew()表示向表中添加一条新的纪录,该函数原型如下:

virtual void AddNew();

执行AddNew()函数之后会新增加一条空记录,等待输入数据。此时记录的每一个字段都被初始化NULL。在输入新的记录数据之后,需调用另一个CRecordset类的函数才能完成对新记录的添加,这个函数是Update(),函数原型为:virtual BOOL Update();

在调用完AddNew函数、输入新数据之后一定要调用Update,它负责把新添加的数据保存到数据源。实际上AddNew函数只是在内存中创建了一块缓冲区,等待输入数据,之后需要使用Update来真正把数据存入数据源。如果在调用Update之前滚动到了另一条记录,那么新记录就会丢失,也不会提出警告。对于dynaset(动态集)类型的记录集,新记录会添加到末尾。新记录不能被添加到Snapshot类型的记录集中。最后还需要调用CRecordset::Requery函数以刷新记录集:virtual BOOL Requery();

Requery函数负责刷新记录集来反映当前最新的数据。在每次对记录进行添加、删除后,都有必要调用Requery来更新记录集。调用Requery函数后,记录集指针重新指向第一条记录。

下面的代码实现了在一个名为OnAdd的函数中添加记录:

void CSampleView::OnAdd()

{

//添加一条新记录

m_pSet->AddNew();

//对记录集中的m_idm_namem_departmentm_age等赋值

m_pSet->m_id 1009;

m_pSet->m_name Jack;

m_pSet->m_department 3;

m_pSet->m_age 20;

m_pSet->m_comment good;

//更新记录集,将新记录存入数据源

m_pSet->Update();

//刷新记录集,并使记录集指针回到第一条记录

m_pSet->Requery();

}

 

删除记录

调用CRecordset类中的Delete函数进行删除记录。该函数用于删除当前记录集指针指向的记录。原型如下:virtual void Delete();

一次成功的删除后,被删除记录的字段全部被设为NULL,必须调用Move函数移动到其他记录上来一处被删除的记录。如果删除不成功,记录中的数据也不会被破坏。一旦移除了删除的记录,就再也不能返回他了。在调用Delete函数时,记录集中必须有一条有效的记录,否则会产生错误。。如果Delete了一条记录,但没有Move到另一条记录就有进行了Delete操作,Delete会产生一个CDBException类的错误。

下面的代码在一个名为OnDelete的函数中实现了删除当前记录的操作:

void CSampleView::OnDelete()

{

//删除当前记录

m_pSet->Delete();

//刷新记录集

m_pSet->Requery();

}

 

修改记录

CRecordset::Edit函数的作用是允许修改当前的记录。原型如下:

virtual void Edit();

调用Edit之后,就可以直接重新设定当前记录中每个字段的值了。再重新设定之后,还需要调用Update函数来保存对数据的修改。实际上,调用Edit之后,要被修改的值先被保存起来。如果Edit之后没有Update,而是移动到另一条记录,那么记录以前的值被重新恢复,不对记录作出修改。或者,调用了一次Edit,而对记录也作出了修改,然后又调用了一次Edit,那么记录还是被恢复到第一次调用Edit之前的值。

下面的代码在名为OnEdit的函数中实现对数据的编辑:

void CSampleView::OnEdit()

{

m_pSet->Edit();

m_pSet->m_id m_newid;

m_pSet->m_name m_newname;

m_pSet->m_department m_newdepartment;

m_pSet->m_age m_newage;

m_pSet->m_comment m_newcomment;

m_pSet->Update();

m_pSet->Requery();

}

直接执行SQL语句(增加、删除表等)

并不是所有的ODBC功能都被数据库类所支持,所以有时候需要使用直接执行SQL语句来对数据库进行一些操作。

CDatabase类中有一个函数ExecuteSQL,通过它可以直接执行SQL语句,对数据库进行操作。原型如下:void ExecuteSQL(LPCTSTR lpszSQL);

参数lpszSQL是一个CString类型的指针,包含一条可执行的、有效地SQL命令。这个函数并不返回信息,如果要对记录进行操作,那么还是要使用记录集对象。

下面的代码可以在一个名为class的表中增加一条记录。m_database是一个CDatabase类的对象。VALUES中是新增记录的具体值,分别于表中的每一个字段相对应。

m_pSet->Open();

m_pSet->AddNew();

m_database.ExecuteSQL(insert into class VALUES(006,Mary,19,FEMALE));

m_pSet.Update();

 

m_pSet->Edit();

m_database.ExecuteSQL(Delete from class WHERE Name Mary’”);

m_pSet.Update();

 

事务处理

事务操作涉及CDatabase类的几个成员函数,BeginTrans表示开始事务,CommitTrans表示接受所有对数据源的修改,或者调用Rollback来终止整个事务。执行事务的三个步骤:

调用CDatabase类对象的BeginTrans成员函数开始事务

调用AddNew->UpdateEdit->UpdateDelete等函数对同一数据库的一个或者多个记录集进行一系列的添加、修改、删除操作

调用CDatabase类的CommitTrans函数执行所有添加、修改、删除操作。如果一次更新出现错误,或者决定取消那些操作,调用Rollback函数。

下面的代码中要删除一家商店中的一项商品,因为要将两个记录集中涉及该商品的所有记录同时删除,所以要使用事务处理。记录集orderset为订单记录,记录集goodsset为商品品种记录。m_shopCDatabase类对象,假设它已经连接到了数据源。strgoodsID为用户输入的要删除商品的ID。两个记录集中都使用m_strFilter变量来过滤出要删除的记录。

BOOL CShopDoc::RemoveGoods(CString strgoodsID)

{ //开始事务

if(!m_shop.BeginTrans())

return FALSE;

//创建订单记录集

COrderSet orderset(&m_shop);

orderset.m_strFilter goodsID = strgoodsID;

if(!orderset.Open(CRecordset::dynaset))

return FALSE;

//创建商品记录集

CGoodsSet goodsset(&m_shop);

goodsset.m_strFilter goodsID = strgoodsID;

if(!goodsset.Open(CRecordset::dynaset))

return FALSE;

//操作中使用了trycatch()来捕获错误信息

//因为有时会经常出现一些意想不到的错误

TRY

{ //删除订单记录

while(!orderSet.IsEOF())

{

orderset.Delete();

orderset.MoveNext();

}

//删除商品条目

goodsset.Delete();

//执行事务

m_shop.CommitTrans();

}

CATCH_ALL(e)

{ //取消事务

m_shop.Rollback();

return FALSE;

}

END_CATCH_ALL

//关闭记录集

orderset.Close();

goodsset.Close();

return TRUE;

}

 

 

使用多记录集

有时候需要使用多个记录集,以使在一个程序中可以操作多个表。生成工程时,通常只选择一个表作为记录集要对应的对象。而且就算在生成工程时选择多个表,那么AppWizard也只会产生多个表的一个笛卡尔乘积,并不会为每一个表产生一个记录集。

通过Visual C++ 6.0ClassWizard来为所需要的每个表创建一个与之对应的记录集。

View à ClassWizard à Add Class à New à New Class 对话框填入NameBase Class中选择CRecordset 单击OK à Database Options选择ODBC作为数据源,在下拉框中选择已注册的某个数据源,Recordset type选择dynaset,单击OK à Select Database Tables对话框,在其中选择想要访问的表后单击OK。这样一个CRecordset类的派生类就创建好了。在需要使用该派生类的类中#include ***.h(该派生类的同文件)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

0

阅读 评论 收藏 转载 喜欢 打印举报
  • 评论加载中,请稍候...
发评论

    发评论

    以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

      

    新浪BLOG意见反馈留言板 电话:4006900000 提示音后按1键(按当地市话标准计费) 欢迎批评指正

    新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 会员注册 | 产品答疑

    新浪公司 版权所有