多层数据库之访问表格的步骤
多层数据库之访问表格的步骤
第八章 访 问 表 格这一章介绍怎样在数据库应用程序中使用TTable构件。TTable的上级是TDBDataSet,而TDBDataSet是从TBDEDataSet继承下来的,TBDEDataSet又是从TDataSet继承下来的。因此,如果对第六章的内容完全掌握了的话,您应该对TTable构件不感到陌生。
8.1 使用TTable构件的一般步骤
TTable构件可以访问数据库表格中的每一行和每一列。TTable构件既可以访问本地的数据库如Paradox、dBASE、Access、FoxPro,也可以访问ODBC数据库,还可以访问远程数据库如InterBase、Sybase和SQL Server。
既可以显示和编辑表格的所有行和所有列,也可以选择一定范围内的行,或者用过滤技术检索出其中一部分行,可以搜索记录,复制、换名或删除一个表格。
一个TTable构件往往只访问一个表格,不过,如果需要的话,一个TTable构件也可以访问几个表格。
8.1.1 设置TTable构件
下面是使用和设置TTable构件的一般步骤,有些应用程序可能还需要其他步骤。
第一步是把一个TTable构件放到窗体或数据模块上,设置它的Name属性指定构件的名称,不能与同一个应用程序中的其他构件同名。
第二步是设置DatabaseName属性指定要访问的数据库。
第三步是设置TableName属性指定要访问的表。
第四步是把一个TDataSource构件放到窗体或数据模块上,设置它的DataSet属性指定TTable构件
第五步是把一个数据控件如TDBGrid放到窗体上,设置它的DataSource属性指定TDataSource构件。
最后,把TTable构件的Active属性设为True。
8.1.2 指定要访问的数据库
DatabaseName属性用于指定要访问的数据库。对于Paradox和dBASE来说,DatabaseName属性可以设为BDE别名或表的路径。对于SQL数据库来说,DatabaseName属性必须设为BDE别名。
用BDE别名指定数据库的好处是,只要修改别名的定义,就可以访问不同的数据库,而应用程序不必作任何修改。要修改别名的定义,就要用到SQLExplorer。
在修改DatabaseName属性之前,先要把Active属性设为False。
如果用TDatabase构件来管理与数据库的连接,可以把DatabaseName属性设为应用程序专用的别名。
8.1.3 指定要访问的表
TableName属性用于指定要访问的表。在设置TableName属性之前,首先要把Active属性设为False,并且设置DatabaseName属性指定要访问的数据库。
既可以在设计期指定要访问的表,也可以在运行期指定要访问的表,程序示例如下:
With OrderOrCustTable Do
Begin
Active := False;
If TableName = 'CUSTOMER.DB' thenTableName := 'ORDERS.DB'
ElseTableName := 'CUSTOMER.DB';
Active := True;
End;
8.1.4 指定表格的类型
如果要访问的数据库是Paradox、dBASE、FoxPro或用逗号隔开的ASCII文本文件,必须设置TableType 属性指定表格的类型。
默认情况下,TableType属性设为ttDefault,BDE将根据文件扩展名来区分表格的类型。扩展名为.DB就认为是Paradox,扩展名为.DBF就认为是dBASE,扩展名为.TXT就认为是ASCII文本,没有扩展名就认为是Paradox。
如果需要确切地指定表格的类型,就必须设置TableType属性,设为ttDefault表示让BDE来判断表格的类型,设为ttParadox表示是Paradox,设为ttDBase表示是dBASE,设为ttFoxPro表示是FoxPro,设为ttASCII表示是ASCII文本。
8.1.5 打开和关闭一个表
要显示和编辑表格中的数据,首先要打开这个表格。要打开一个表格有两种方式,一是把Active属性设为True,二是调用Open。表格打开后,数据集就进入dsBrowse状态。
如果不再需要显示和编辑表格中的数据,或者要修改TTable构件的某些属性如DatabaseName、TableName和TableType,此时就要关闭表格。
要关闭表格也有两种方式,一是把Active属性设为False,二是调用Close函数。表格关闭后,将使数据集进入dsInactive状态。
8.1.6 控制对表格的读写
当一个表格打开后,应用程序需要取得对它读和写的访问权,这就要用到TTable构件的三个属性:CanModify、ReadOnly和Exclusive。
CanModify是一个只读的属性,当一个表格打开后,可以通过这个属性来判断表格的数据是否修改。如果CanModify属性返回False,应用程序就不能修改表格的数据。
如果CanModify属性返回True,应用程序可以修改表格的数据。
如果ReadOnly属性设为False,用户就可以编辑表格的数据。如果要限制用户只能查看数据,不能修改数据,就得把ReadOnly属性设为True。
Exclusive属性用于设置应用程序是否能独占表格。如果要独占对表格的访问权,就在打开表格之前把Exclusive属性设为True,其他应用程序就既不能读也不能写这个表格。
Exclusive属性一般只适用于Paradox、dBASE或FoxPro等本地数据库,对于SQL数据库来说,有的服务器不支持表格级的锁定,有的服务器虽然支持锁定,但其他应用程序仍然可以读表格的数据。
8.2 在表格中搜索记录
要在表格中搜索记录有几种方式,我们推荐调用Locate函数和Lookup函数,这两个函数可以在任何表格中基于任何类型的字段来搜索记录,不管表格是否建立了索引。
Locate函数用于在表格中搜索一条符合特定条件的记录,如果找到的话,就使该记录成为当前记录。
Lookup函数与Locate 函数的区别是,如果找到匹配的记录后,当前记录不变。事实上,Locate函数和Lookup函数并不仅适用于TTable构件,它还适用于其他数据集构件。
8.2.1 基于索引中的字段搜索
记录除了Lookup函数与Locate 函数外,TTable构件还保留了Goto系列和Find系列的方法,这些方法可以基于索引中的字段搜索匹配的记录,并使找到的记录成为当前记录。在Delphi 4中,索引中的字段称为关键字段。
与Locate不同的是,GotoKey、GotoNearest要求表格必须已建立了索引,并且只能基于关键字段搜索记录,而Locate则没有这个要求。下面列出了Goto系列和Find系列中的6种方法:
.EditKey使数据集进入dsSetKey状态,程序就可以指定关键字段的值。
.FindKey综合了SetKey和GotoKey两个方法。
.FindNearest综合了SetKey和GotoNearest两个方法。
.GotoKey基于关键字段搜索特定的记录,并使找到的记录成为当前记录。
.GotoNearest类似于GotoKey,但只要近似匹配即可(适合于字符串类型的字段)。
.SetKey使数据集进入dsSetKey状态,程序就可以指定关键字段的值。与EditKey不同的是,如果SetKey没有调用成功,原先的键值被清掉。
其中,GotoKey和FindKey是返回布尔值的函数,如果调用成功,就返回True,并使找到的记录成为当前记录。如果没有找到匹配的记录,这两个函数就返回False。
GotoNearestand和FindNearest分别类似于GotoKey和FindKey,不同的是,如果它们没有找到精确匹配的记录,就查找近似匹配的记录。
8.2.2 Goto系列
使用Goto系列的方法来搜索和定位记录,一般是这么几个步骤:
第一步是设置IndexName属性指定要使用的索引(对SQL数据库来说,需要设置IndexFieldNames属性)。当然,如果使用表格本身的主索引,可以不设置这个属性。
第二步是打开表格。
第三步是调用SetKey使数据集进入dsSetKey状态。
第四步是通过Fields属性或FieldByName函数指定关键字段的值。
第五步是调用GotoKey或GotoNearest查找匹配的记录。
下面的例子实际演示了上述5个步骤:
Procedure TSearchDemo.SearchExactClick(Sender: TObject);
Begin
Table1.SetKey;
Table1.Fields[0].AsString := Edit1.Text;
If not Table1.GotoKey then ShowMessage('Record not found');
End;
上述例子演示的是GotoKey,其实GotoNearest也是差不多的,程序示例如下:
Table1.SetKey;
Table1.Fields[0].AsString := 'Sm';
Table1.GotoNearest;
8.2.3 Find系列使用
Find系列的方法来搜索和定位记录,一般是这么几个步骤:
第一步是设置IndexName属性指定要使用的索引(对SQL数据库来说,需要设置IndexFieldNames属性)。当然,如果您使用表格本身的主索引,可以不设置这个属性。
第二步是打开表格。
第三步是调用FindKey或FindNearest函数查找匹配的记录。这两个方法都要传递一个KeyValues参数,用于指定关键字段的值。其中,FindNearest只适合于字符串类型的字段。
8.2.4 KeyExclusive属性和KeyFieldCount属性
调用GotoNearest、FindNearest等函数时,如果KeyExclusive属性设为False,匹配的记录将成为当前记录(如果找到的话); 如果KeyExclusive属性设为True,匹配的记录的下一条记录成为当前记录。
如果一个索引中有多个字段,而您只想基于其中部分字段进行搜索,就要设置KeyFieldCount属性指定关键字段的个数。例如,假设索引中有三个关键字段,而您只想基于其中第一个字段进行搜索,就把KeyFieldCount属性设为1。
如果把KeyFieldCount属性设为2,表示基于前面2个字段进行搜索。如果把KeyFieldCount属性设为3,表示基于前面3个字段进行搜索。但是,如果要基于第一个和第三个字段进行搜索,KeyFieldCount属性无能为力。
8.2.5 基于副索引进行搜索
如果不想基于主索引而想基于副索引进行搜索,就要通过IndexName属性指定索引名称。在设置IndexName属性之前,首先要关闭表格。程序示例如下:
Table1.Close;
Table1.IndexName := 'CityIndex';
Table1.Open;
Table1.SetKey;
Table1['City'] := Edit1.Text;
Table1.GotoNearest;
对于SQL表来说,可以直接用IndexFieldNames属性指定索引中的字段。对于Paradox和dBASE表来说,必须事先建立索引,而且只能基于索引中的字段进行搜索,否则,会触发异常。
每次调用SetKey或FindKey函数,总是清除前一次对键值的设置。如果想基于前面设置的键值继续搜索,或者要在原来的基础上加入新的关键字段,就不能调用SetKey或FindKey,而要调用EditKey函数。例如,假设原先设置City和Country两个键值,现在要增加一个Company字段,程序示例如下:
Table1.EditKey;
Table1[' Company] := Edit2.Text;
Table1.GotoNearest;
8.3 对记录排序
索引的作用就是对记录排序。默认情况下,Paradox表按照主索引以升序排列,如果要以其他方式排序,就要用到副索引(Paradox或dBASE)或者直接列出索引中的字段(SQL)。
8.3.1 检索已定义的索引名
可以调用GetIndexNames函数来检索一个表格已定义的索引名,它能够返回一个列表,该列表由所有已定义的索引的名称组成。程序示例如下:
varIndexList: TList;
...
CustomersTable.GetIndexNames(IndexList);
注意:对于Paradox来说,主索引是没有名称的,因此,GetIndexNames函数返回的列表中不包含主索引。
8.3.2 指定使用哪个副索引
对于Paradox表来说,如果要按副索引排序,首先要设置IndexName属性指定一个副索引。例如:
CustomersTable.IndexName := 'CustDescending';
如果先按副索引排序,以后又想按主索引排序,只要把IndexName属性设为空。
对于dBASE表来说,如果要按副索引排序,首先要设置IndexFiles属性指定一个或几个索引文件。
在设计期,可以在对象观察器中单击IndexFiles属性边上的省略号按钮,打开如图8.1所示的对话框:
图8.1在设计期设置IndexFiles属性
在运行期,可以通过TStrings对象动态地增加或删减索引文件,也可以用一个字符串列出要使用的索引文件名,文件名与文件名之间用分号隔开。
下面的代码指定了一个索引文件ANIMALS.MDX,并且指定其中的NAME作为副索引来排序:
AnimalsTable.IndexFiles := 'ANIMALS.MDX';
AnimalsTable.IndexName := 'NAME';
对于SQL表来说,记录的排列顺序取决于SQL语句的ORDER BY部分。可以设置IndexName属性指定一个已有的索引,也可以设置IndexFieldNames属性直接指定索引中的字段,让SQL表按这些字段排序。
IndexName属性和IndexFieldNames属性是互斥的,设置其中一个,另一个就被清空。
IndexFieldNames属性是一个字符串列表,可以列出所有要用来排序的字段的名称,彼此之间用分号隔开。只能以升序排列,至于是否大小写敏感,取决于服务器。
下面的代码试图把PhoneTable表先按LastName字段再按FirstName字段排序:
PhoneTable.IndexFieldNames := 'LastName;FirstName';
注意:如果对Paradox和dBASE表使用IndexFieldNames属性,Delphi 4将试图查找有没有类似的索引,如果没有,就触发异常。
8.3.3 检查索引中的字段
如果想知道一个索引中有几个字段、有哪些字段,可以使用IndexFieldCount属性和IndexFields属性。
IndexFieldCount属性能够返回索引中的字段的个数。IndexFields属性是一个字符串列表,由索引中的字段的名称组成。程序示例如下:
varI: Integer;
ListOfIndexFields: array[0 to 20} of string;
Begin
With CustomersTable Do
Begin
For I := 0 to IndexFieldCount - 1 Do
ListOfIndexFields[I] := IndexFields[I];
End;
End;
8.4 选择部分记录
数据库往往是庞大的,而一个应用程序通常只关心其中一部分数据。对于TTable构件来说,要限制应用程序能访问的记录有两种方式。一是使用过滤技术,它不但适用于TTable,也适用于TQuery和TStoredProc。二是设置范围,它只适用于TTable构件。
8.4.1 过滤和范围的区别
过滤和范围都是为了选择一部分记录,但它们的工作方式是不同的。
所谓范围,是指一段连续的记录,这些记录的值在一定的范围内。例如,假设有一个雇员表格,本来按LastName字段排序,如果关心LastName字段的值在“Jones”和“Smith”之间的记录,就需要设置范围。只能对排序的Paradox和dBASE表格设置范围,对SQL表格,也可以对IndexFieldNames属性中出现的字段设置范围。
而过滤则不需要依赖于索引,它类似于SQL Select语句,只选择满足特定条件的记录,这些记录不一定是连续的。例如,假设有一个雇员表格,可以用过滤技术找出其中出生在California并且在公司任职超过5年以上的雇员。
从功能上讲,过滤的功能比设置范围的功能强。不过,对于一个很大的数据库并且记录已经按某个字段排序,用范围来选择部分记录比较快。
8.4.2 设置范围的起点
要设置范围的起点,就要调用SetRangeStart。SetRangeStart将使数据集进入dsSetKey状态。一旦调用了SetRangeStart后,以后对字段的赋值就认为是范围的起点。对于Paradox 和dBASE来说,调用SetRangeStart后,您只能对关键字段赋值。
例如,假设有一个CUSTOMER表,基于CustNo字段排序。可以这样设置范围的起点和终点:
With Customers Do
Begin
SetRangeStart;FieldByName('CustNo') := StartVal.Text;
SetRangeEnd;
If EndVal.Text <> '' then
FieldByName('CustNo') := EndVal.Text;
ApplyRange;
End;
读者可能注意到,在调用了SetRangeEnd后,首先检查用户是否在编辑框EndVal中输入了范围的终点,这是因为,如果范围的终点设为NULL,将不会有任何记录被选择。
如果索引中有多个字段,可以在调用了SetRangeStart后对所有字段赋值,也可以只对部分字段赋值。如果某个字段没有被赋值,就认为该字段的值是NULL。如果在调用了SetRangeStart后对不是索引中的字段赋值,则是无效的。
如果您不想设置范围的起点,只想设置终点,可以不调用SetRangeStart。
8.4.3 设置范围的终点
要设置范围的终点,就要调用SetRangEnd函数。SetRangeEnd将使数据集进入dsSetKey状态。一旦调用了SetRangeEnd函数后,以后对字段的赋值就认为是范围的终点。对于Paradox 和dBASE来说,调用SetRangeEnd函数后,只能对关键字段赋值。
注意:范围的起点可以省略,但终点不能省略,即使终点无关紧要。如果没有设置终点,将认为终点是NULL,此时,将不会有任何记录被选择。下面这个例子基于LastName字段设置范围:
With Table1 Do
Begin
SetRangeStart;
FieldByName('LastName') := Edit1.Text;
SetRangeEnd;FieldByName('LastName') := Edit2.Text;
ApplyRange;
End;
如果索引中有多个字段,可以在调用SetRangeEnd函数后对所有字段赋值,也可以只对部分字段赋值。如果某个字段没有被赋值,就认为该字段的值是NULL。如果在调用了SetRangeEnd后对不是索引中的字段赋值,将触发异常。
8.4.4 同时设置起点和终点
其实,不必分别调用SetRangeStart和SetRangeEnd来设置范围的起点和终点,只要调用SetRange就可以同时设置起点和终点。SetRange将使数据集进入dsSetKey状态。
SetRange需要传递两个数组类型的参数,一个用于设置起点,另一个用于设置终点,程序示例如下:
SetRange([Edit1.Text, Edit2.Text], [Edit3.Text, Edit4.Text]);
如果索引中有多个字段,您可以在调用SetRange时对所有字段赋值,也可以只对部分字段赋值。如果某个字段没有被赋值,就认为该字段的值是NULL。在调用SetRange 时对不是索引中的字段赋值是无效的。
注意:调用SetRange时,范围的起点可以省略,但终点不能省略,即使终点是无关紧要的。如果您没有设置终点,将认为终点是NULL,此时,将不会有任何记录被选择。
8.4.5 近似匹配
如果组成索引的字段是字符串类型,可以在调用SetRangeStart、SetRangeEnd或SetRange函数时给出字段的近似值,程序示例如下:
Table1.SetRangeStart;Table1['LastName'] := 'Smith';
Table1.SetRangeEnd;Table1['LastName'] := 'Zzzzzz';
Table1.ApplyRange;
下列代码将使所有LastName字段的值大于“Sm”并且FirstName字段的值大于“J”的记录被选择。
Table1.SetRangeStart;Table1['LastName'] := 'Sm';
Table1['FirstName'] := 'J';
Table1.SetRangeEnd;Table1['LastName'] := 'Zzzzzz';
Table1.ApplyRange;
8.4.6 应用和取消范围
调用SetRangeStart、SetRangeEnd或SetRange只是设置了范围的起点和终点,但还没有真正地使范围有效,还需要调用ApplyRange函数。
调用了ApplyRange 后,应用程序只能工作于一定范围内的记录。如果以后又想工作于所有的记录,可以调用CancelRange。不过,调用了CancelRange后,原先设置的起点和终点仍然保留,因为下次还可能要用到它们。起点和终点的设置将一直保留,除非设置了新的起点和终点。
8.4.7 修改范围的起点和终点
要修改原有的起点和终点,可以调用EditRangeStart和EditRangeEnd函数。调用EditRangeStart和EditRangeEnd将使数据集处于dsSetKey状态。
一旦调用EditRangeStart后,以后对字段的赋值就认为是范围的起点。对于Paradox 和dBASE来说,调用EditRangeStart后,只能对关键字段赋值。
一旦调用EditRangeEnd后,以后对字段的赋值就认为是范围的终点。对于Paradox 和dBASE来说,调用EditRangeEnd后,只能对关键字段赋值。
8.5 对表格整体的操作
下面介绍几个对表格整体的操作,包括删除表格中的所有记录、删除表格、给表格换名、创建新的表格、两个表格之间保持同步。
8.5.1 删除表格中的所有记录
要删除表格中的所有记录,可以调用EmptyTable函数。对于SQL表来说,必须拥有删除记录的权限。用EmptyTable删除的记录是不能恢复的。
在调用EmptyTable之前,必须先设置DatabaseName、TableName和TableType等属性,如果表处于打开状态,必须是以独占方式打开的。程序示例如下:
Procedure TForm1.Button1Click(Sender: TObject);
Begin
With Table1 Do
Begin
Active := False;
DatabaseName := 'Delphi_Demos';
TableName := 'CustInfo';
TableType := ttParadox;
EmptyTable;
End;
End;
8.5.2 删除一个表格
在设计期,要删除一个表格,可以用鼠标右键单击TTable构件,在弹出的菜单中选择“Delete Table”命令。当然,必须事先设置了DatabaseName属性和TableName属性,弹出的菜单中才会有“Delete Table”命令。
在运行期,要删除一个表格,可以调用DeleteTable。注意:用DeleteTable删除的表格是不可恢复的。在调用DeleteTable函数之前,必须设置DatabaseName、TableName和TableType属性,并且表格必须处于关闭状态。程序示例如下:
With Table1 Do
Begin
Active := False;
DatabaseName := 'DBDEMOS';
TableName := 'Customer';
TableType := ttParadox;
DeleteTable;
End;
8.5.3 给表格换名
在设计期,要给一个Paradox或dBASE表换名,可以用鼠标右键单击TTable构件,在弹出的菜单中选择“Rename Table”命令。
在运行期,要给一个Paradox或dBASE表换名,可以调用RenameTable函数,例如:
Customer.RenameTable('CustInfo');
8.5.4 创建表格
既可以在设计期也可以在运行期创建表格。通过“Create Table”命令(设计期)或CreateTable函数(运行期),无须具备SQL的知识就能创建一个表格。不过,必须对数据集构件的有关属性、方法和事件比较熟悉,尤其是对TTable。
下面是创建一个表格的一般步骤:
第一步是设置DatabaseName属性指定要把表格放到哪个数据库中。
第二步是设置TableType属性指定表格的类型,如果是Paradox、dBASE或ASCII文本,就把TableType属性设为ttParadox、ttDBase或ttASCII。如果是其他类型,就把TableType属性设为ttDefault。
第三步是设置TableName属性指定表格的名称。如果TableType属性设为ttParadox或ttDBase,您不必给出扩展名。
第四步是建立字段定义。在设计期,您可以在对象观察器中单击FieldDefs属性边上的省略号按钮,Delphi 4将打开一个如图8.2所示的编辑器:
图8.2 建立字段定义
单击工具栏上的按钮可以创建一个新的字段,单击按钮可以删除一个字段,单击按钮可以把字段的顺序上移,单击按钮可以把字段的顺序下移。
选择其中一个字段,就可以在对象观察器中设置字段(TFieldDef对象)的定义。
在运行期,可以调用TFieldDefs的Add函数创建一个字段。
第五步是建立索引定义(可选)。在设计期,您可以在对象观察器中单击IndexDefs属性边上的省略号按钮,Delphi 4将打开一个如图8.3所示的编辑器:
图8.3 建立索引定义
单击工具栏上的按钮可以创建一个新的索引,单击按钮可以删除一个索引,单击按钮可以把索引的顺序上移,单击按钮可以把索引的顺序下移。
选择其中一个字段,就可以在对象观察器中设置索引(TIndexDef对象)的定义。
在运行期,可以调用TIndexDefs的Add函数创建一个新的索引。
注意:在设计期,可以借用一个已有的表格中的字段定义和索引定义。首先要设置DatabaseName属性和TableName属性指定一个已有的表格,然后在TTable构件上单击鼠标右键,在弹出的菜单中选择“Update Table Definition”命令,此命令将使这个表格中的字段定义和索引定义读到FieldDefs属性和IndexDefs属性中。接着,按第一步和第二步设置DatabaseName属性和TableName属性。
第六步是创建这个表格。在设计期,可以用鼠标右键单击TTable构件,在弹出的菜单中选择“Create Table”命令。在运行期,可以调用CreateTable函数。
注意:如果新建的表格与一个已有的表格重名,已有的表格将被覆盖,并且无法恢复。
下面的代码演示了怎样在运行期动态地创建一个表格:
var
NewTable: TTable;
NewIndexOptions: TIndexOptions;
TableFound: Boolean;
Begin
NewTable := TTable.Create;
NewIndexOptions := [ixPrimary, ixUnique];
With NewTable Do
Begin
Active := False;
DatabaseName := 'DBDEMOS';
TableName := Edit1.Text;
TableType := ttDefault;
FieldDefs.Clear;
FieldDefs.Add(Edit2.Text, ftInteger, 0, False);
FieldDefs.Add(Edit3.Text, ftInteger, 0, False);
IndexDefs.Clear;
IndexDefs.Add('PrimaryIndex? Edit2.Text, NewIndexOptions);
End;
TableFound := FindTable(Edit1.Text);
If TableFound = True then
If MessageDlg('Overwrite existing table ' + Edit1.Text + '?', mtConfirmation,mbYesNo, 0) = mrYes then
TableFound := False;
If not TableFound then
CreateTable;
End;
8.5.5 两个表格之间保持同步
多个TTable构件可以指向同一个表格,即它们的DatabaseName属性和TableName属性相同。每个TTable构件所连接的TDataSource构件可以不同,也就是说,它们完全可以独立地工作,在不同的TTable 构件中,当前记录的位置是不同的。例如,在Table1中,第2条记录是当前记录,而在Table2中,第3条记录是当前记录。
不过,可以调用GotoCurrent函数使多个TTable构件保持同步。例如,CustomerTableOne中第2条记录是当前记录,而在CustomerTableTwo中,第3条记录是当前记录。可以这样调用GotoCurrent:
CustomerTableOne.GotoCurrent(CustomerTableTwo);
如果要同步的两个TTable构件不在同一个数据模块或窗体内,可以这样写:
CustomerTableOne.GotoCurrent(Form2.CustomerTableTwo);
8.6 Master/Detail关系
TTable构件的MasterSource属性和MasterFields属性可以用来建立一对多的Master/Detail关系。
这两个属性都可用于Detail表。其中,MasterSource属性用于指定一个TDataSource构件,该构件的DataSet属性指定了Master表。
MasterFields属性用于指定一个或几个字段,这些字段用于连接Master表和Detail表。如果有多个字段的话,彼此之间要用分号隔开,例如:
Table1.MasterFields := 'OrderNo;ItemNo';
下面我们举例说明建立Master/Detail关系的一般步骤。假设Master表叫CustomersTable,Detail表叫OrdersTable。
第一步,把两个TTable构件和两个TDataSource构件放到窗体上。
第二步,把第一个TTable构件的DatabaseName属性设为DBDEMOS,把TableName属性设为CUSTOMER,把Name属性设为CustomersTable。
第三步,把第二个TTable构件的DatabaseName属性设为DBDEMOS,把TableName属性设为ORDERS,把Name属性设为OrdersTable。
第四步,把第一个TDataSource构件的Name属性设为CustSource,把DataSet属性设为CustomersTable。
第五步,把第二个TDataSource构件的Name属性设为OrdersSource,把DataSet属性设为OrdersTable。
第六步,把两个TDBGrid构件放到窗体上,把其中第一个TDBGrid构件的DataSource属性设为CustSource,把第二个TDBGrid构件的DataSource属性设为OrdersSource。
第七步,把OrdersTable的MasterSource属性设为CustSource,这就把CUSTOMER表与ORDERS表连接起来。
第八步,单击OrdersTable的MasterFields属性边上的省略号按钮打开“FieldLink Designer”对话框,在“Available Indexes”框内选择CustNo,然后在“DetailFields”框和“Master Fields”框都选择CustNo,单击“Add”按钮。
第九步,把CustomersTable和OrdersTable的Active属性设为True。
第十步,编译和运行这个程序。您将发现,在第一个栅格中浏览CUSTOMER表时,第二个栅格中的ORDERS表将显示当前客户的所有订单。
8.7 嵌 套 表
Delphi 4用TNestedTable构件来访问嵌套表中的数据,TNestedTable是从TBDEDataSet继承下来的。下面是访问嵌套表的一般步骤:
第一步,把一个数据集构件如TTable、TQuery放到窗体或数据模块上,这个数据集中必须包含有TDataSetField或TReferenceField类型的字段。
第二步,把一个TNestedTable构件放到窗体或数据模块上,设置它的DataSetField属性指定要访问的TDataSetField或TReferenceField类型的字段,可以从下拉列表中选择。
第三步,把一个TDataSource构件放到窗体或数据模块上,设置它的DataSet属性指定TNestedTable构件。
第四步,把一个数据控件如TDBGrid构件放到窗体或数据模块上,设置它的DataSource属性指定TDataSource构件。
第五步,在设计期或运行期把TNestedTable构件的Active属性设为True。
8.8 从另一个表格中引入数据
TTable构件的BatchMove函数能够从其他表中引入数据,包括复制、添加、修改或删除记录,并返回被操作的记录数。
BatchMove函数需要传递两个参数,一个是源数据集的名称,另一个是操作方式。BatchMove函数支持以下操作方式:
.batAppend把源表中的所有记录复制到目标表的末尾;
.batUpdate用源表中的记录更新目标表中的记录;
.batAppendUpdate把源表中不存在于目标表中的记录添加在目标表的末尾;
.batDelete删除源表在目标表中重复的记录;
.batCopy把源表复制到目标表。
下面的代码用Customer表中的记录更新当前表中的记录:
Table1.BatchMove('CUSTOMER.DB', batUpdate);
与TBatchMove构件相比,BatchMove函数的功能比较简单。如果要移动很多的记录,最好选用TBatchMove构件。
8.9 使用TBatchMove
TBatchMove构件封装了一部分BDE的功能,能够从另一个数据集中引入数据。TBatchMove构件经常用于从服务器下载数据或者把本地的数据上载到服务器。
TBatchMove构件能够自动建立一个新的表,并映射源数据集中的字段名称和类型。
8.9.1 使用TBatchMove构件的一般步骤
第一步,把一个TTable构件或TQuery构件放到窗体或数据模块上作为源数据集。
第二步,把另一个TTable构件放到窗体或数据模块上作为目标数据集。
第三步,把一个TBatchMove 构件放到数据模块上,设置它的Source属性指定源数据集,设置它的Destination属性指定目标数据集。
第四步,设置Mode属性指定操作方式,可以设为batAppend、batUpdate、batAppendUpdate、batCopy、batDelete等。
第五步(可选),设置ProblemTableName属性指定一个表的名字,执行批量移动操作时有问题的记录将放到这个表中。设置KeyViolTableName属性指定一个表的名字,更新一个Paradox表时违反主索引定义的记录将放到这个表中。设置ChangedTableName 属性指定一个表的名字,执行批量移动操作时目标表中变动的记录将放到这个表中。
第六步(可选),设置Mappings属性指定字段的映射方式。
第七步,调用Execute执行批量移动操作。
8.9.2 指定操作方式
TBatchMove构件的Mode属性用于指定操作方式:
.batAppend把源表中的所有记录复制到目标表的末尾;
.batUpdate用源表中的记录更新目标表中的记录;
.batAppendUpdate把源表中不存在于目标表中的记录添加在目标表的末尾;
.batDelete删除源表在目标表中重复的记录;
.batCopy把源表复制到目标表。
对于batAppend方式来说,目标数据集必须是已存在的。如果需要的话,BDE会自动进行数据类型的转换,不过,转换并不总是能成功的。对于batUpdate方式来说,目标数据集必须是已存在的,并且必须已建立索引。TBatchMove构件根据关键字段用源数据集中的记录更新目标数据集中匹配的记录。在更新过程中,BDE会尽可能地转换数据类型。
对于batAppendUpdate方式来说,目标数据集必须是已存在的,并且必须已建立索引。TBatchMove构件根据关键字段用源数据集中的记录更新目标数据集中匹配的记录,如果没有找到匹配的记录,TBatchMove构件就把记录添加在源数据集的末尾。
对于batCopy方式来说,目标数据集最好是不存在的,如果已经存在,目标数据集中的记录会被源数据集中的记录覆盖。如果源数据集和目标数据集的结构不同,例如,一个是Paradox,另一个是InterBase,BDE会尽可能地转换。不过,TBatchMove构件不会复制源数据集的索引、纠错规则、存储过程。
对于batDelete方式来说,目标数据集必须是已存在的,并且必须已建立索引。TBatchMove构件根据关键字段删除目标数据集中重复的记录,
8.9.3 映射字段类型
默认情况下,在源数据集和目标数据集之间批量移动记录时是以字段的位置匹配的,也就是说,源数据集中的第一个字段到目标数据集中仍然是第一个字段,依次类推。如果不希望按字段的位置匹配,而希望按字段的名称匹配,就要设置Mappings属性。
Mappings属性是一个字符串列表。假设源数据集中有一个字段叫SourceColName,希望它在目标数据集中匹配DestColName字段,Mapping属性中应加入这么一行:
DestColName = SourceColName
程序示例如下:
Procedure TForm1.Button1Click(Sender: TObject);
var Maps: TStringList;
Begin
With Maps Do
Begin
Clear;Add('CustNo=CustomerNum');
Add('SSN');
End;
BatchMove1.Mappings := Maps;
End;
如果等号两边的字段的数据类型不同,Delphi 4会尽可能地转换,但可能会丢失数据或精度。假设一个字段是CHAR(10),如果试图转换为CHAR(5),后5个字符将被截掉。
8.9.4 执行批量移动操作
调用Execute函数将执行批量移动操作。例如,假设BatchMoveAdd是TBatchMove构件的名称,要执行批量移动操作,可以这样写:
BatchMoveAdd.Execute;
实际上,在设计期也可以执行批量移动操作,方法是:在TBatchMove 构件上单击鼠标右键,在弹出的菜单中选择“Execute”命令。
调用了Execute后,可以访问MovedCount属性,查看实际移动了多少条记录。
RecordCount属性用于限制每次批量移动的记录数。如果RecordCount属性设为0,表示没有限制。
8.9.5 处理错误
在进行批量移动操作的过程中,有可能发生两种类型的错误,一是数据类型转换错误,还有一种是违反数据完整性。
如果AbortOnProblem属性设为True,只要遇到一个数据类型转换错误就会停止批量移动操作。如果AbortOnProblem属性设为False,即使遇到数据类型转换错误也不会停止批量移动操作。可以在ProblemTableName属性指定的表中找到这些有错误的记录。
ProblemCount属性返回有错误的记录数。如果AbortOnProblem属性设为True,ProblemCount属性最多返回1,因为一旦遇到一个错误,操作就停止了。
AbortOnKeyViol属性类似于AbortOnProblem属性,如果AbortOnKeyViol属性设为True,只要遇到一个违反数据完整性的错误就会停止批量移动操作。
可以设置ChangedTableName属性指定一个表的名称,让TBatchMove构件把目标数据集中所有变化的记录写到这个表中。
可以设置KeyViolTableName属性指定一个表的名称,让TBatchMove构件把所有违反数据完整性的记录放到这个表中。
可以设置ProblemTableName属性指定一个表的名称,让TBatchMove构件把所有有数据类型转换错误的记录写到这个表中。