在前面的范例应用程序中,主窗体同时显示了三个数据表的数据、书籍、出版商和作者。但是在前面ApplyUpdates按钮的OnClick事件处理函数只调用了cdsBooks的ApplyUpdates方法,因此,即使用户同时修改了书籍和出版商的数据并且点击ApplyUpdates按钮,那么,也只有书籍的数据会更新回BOOKS数据表中,出版商的数据并不会更新回PUBLISHERS,因为范例应用程序并没有调用cdsPublishers的ApplyUpdates方法。那么范例应用程序要如何才能够同时使书籍和出版商的数据更新回这两个数据表呢?
也许你会认为很简单,只要在ApplyUpdates按钮的OnClick事件处理函数中加入调用cdsPublishers的ApplyUpdates方法即可。但如果只是如此做的话,那么可能会发生问题,因为每一个ApplyUpdates方法会产生一个独立的数据库事务(Transaction),因此当cdsBooks和cdsPublishers分别调用ApplyUpdates方法时,便会启动两个独立的事务。因此,如果用户同时更新了书籍和出版商的数据并且更新回数据表中,那么就有可能书籍数据更新成功,而出版商更新失败。如果我们希望在更新这两个数据表的数据时,一定要同时成功;或是在任何一个数据表更新失败时,必须恢复到进行更新操作之前的状态,那么,这两个更新的操作就必须包含在一个相同的数据库事务之中。如果发生错误,数据库事务可以保证在同一个事务的范围内,把数据恢复到原先的状态。TSQLConnection控件的StartTransaction方法可以启动一个独立的数据库事务,我们可以先调用它,随后再更新数据表的数据,如果数据表都成功地更新,就可以调用TSQLConnection的Commit方法保证同时更新成功。如果任何数据表发生错误,可以调用TSQLConnection的Rollback方法恢复数据。
数据库事务会在稍后第4章详细说明。
TSQLConnection的StartTransaction方法原型如下:
procedure StartTransaction(TransDesc: TTransactionDesc);
它接受一个描述事务内容的参数TransDesc。TransDesc的类型是TTransactionDesc,在TTransactionDesc中用户可以指定特定的事务ID以及事务的层级。TSQLConnection之所以需要这个参数是因为dbExpress支持嵌套事务,这是比BDE先进的地方,在第4章中会继续讨论dbExpress的事务管理功能。
了解了如何把更新数据表的操作包含在事务范围之中后,我们就可以修改主窗体中ApplyUpdates按钮的OnClick事件处理函数,代码如下:
procedure TForm1.bbtnApplyClick(Sender: TObject);
var
aTD: TTransactionDesc;
begin
if (not dmDemo1.scnnDemo.InTransaction) then
begin
aTD.TransactionID := 1;
aTD.IsolationLevel := xilREADCOMMITTED;
dmDemo1.scnnDemo.StartTransaction(aTD);
try
if (dmDemo1.scdsBooks.ChangeCount > 0) then
dmDemo1.scdsBooks.ApplyUpdates(0);
if (dmDemo1.scdsPublishers.ChangeCount > 0) then
dmDemo1.scdsPublishers.ApplyUpdates(0);
dmDemo1.scnnDemo.Commit(aTD);
except
on e: Exception do
begin
ShowMessage(e.message);
dmDemo1.scnnDemo.Rollback(aTD);
end;
end;
end;
ShowChangeCount;
end;
上面的程序代码首先借助TSQLConnection的InTransaction属性值来判断是否还有未结束的事务。如果没有的话,就设定一个ID为1而事务层级为xilREADCOMMITTED的TTransactionDesc对象,调用TSQLConnection的StartTransaction方法并且传入此对象。接着程序判断cdsBooks中是否有任何被变更过的数据,如果有的话,就调用它的ApplyUpdates方法。我们也同时对cdsPublishers进行一样的操作。最后如果两个数据表都成功地更新,就调用TSQLConnection的Commit方法确定更新操作的完成。当然,如果发生任何的错误,程序执行权便会移转到except部分。在这里程序代码先显示发生的错误信息,再调用TSQLConnection的Rollback方法恢复数据到进行更新操作之前的状态。由于TTransactionDesc定义是在DBXpress程序单元中,因此读者要记得在uses子句中加入DBXpress这个程序单元。
最后再修改ShowChangeCount让它也在TStatusBar中显示用户对于出版商变更的数据笔数。
procedure TForm1.ShowChangeCount;
begin
StatusBar1.Panels[0].Text :=
'目前被变更的数据笔数:' + IntToStr(dmDemo1.cdsBooks.ChangeCount);
StatusBar1.Panels[1].Text :=
'出版商被变更的数据笔数:' + IntToStr(dmDemo1.cdsPublishers.ChangeCount);
end;
现在再执行范例应用程序,并且同时变更书籍和出版商的数据。此时范例应用程序看起来如图2-16所示。请注意下方的TStatusBar显示了这两个数据表目前被变更的笔数。此时如果再点击主窗体中的ApplyUpdates按钮,那么数据应该会成功地更新回这两个数据表中。如果观察TSQLMonitor控件的追踪信息,便可以看到如下的程序代码,证明这两个更新的操作是发生在一个相同的数据库事务中。
INTERBASE - isc_start_transaction
INTERBASE - isc_dsql_allocate_statement
update "BOOKS" set
"BOOKNAME" = ?
where
"ISBN" = ? and
"BKID" = ? and
"BOOKNO" = ? and
"BOOKNAME" = ? and
"VID" = ? and
"CATEGORY" = ? and
"CLEVEL" = ? and
"AID" = ?
INTERBASE - isc_dsql_prepare
INTERBASE - isc_dsql_sql_info
INTERBASE - isc_vax_integer
INTERBASE - isc_dsql_describe_bind
INTERBASE - SQLDialect = 3
INTERBASE - isc_dsql_execute
INTERBASE - isc_dsql_free_statement
INTERBASE - isc_dsql_free_statement
INTERBASE - isc_dsql_allocate_statement
update "PUBLISHERS" set
"ADDRESS" = ?
where
"VID" = ? and
"NAME" = ? and
"ADDRESS" = ? and
"PHONE" = ? and
"EMAIL" = ? and
"PKIND" = ?
INTERBASE - isc_dsql_prepare
INTERBASE - isc_dsql_sql_info
INTERBASE - isc_vax_integer
INTERBASE - isc_dsql_describe_bind
INTERBASE - SQLDialect = 3
INTERBASE - isc_dsql_execute
INTERBASE - isc_dsql_free_statement
INTERBASE - isc_dsql_free_statement
INTERBASE - isc_commit_transaction

图2-16 在范例应用程序中同时变更BOOKS和PUBLISHERS两个数据表的数据
现在的范例应用程序已经很完整了,它使用了数个经常会用来开发数据库应用程序的技巧,也证明了前面讨论的内容。读者可以继续修改这个范例,例如如果用户也变更了作者信息,那么该如何把作者的数据更新回PERFORMERS数据表呢?






