交通信号灯品牌:clientdataset的使用

来源:百度文库 编辑:偶看新闻 时间:2024/04/26 00:46:22
clientdataset的使用(一)
Delphi做为一个快速应用开发工具,深受程序员的喜爱。其强大的组件功能,让程序员能够轻松、

高效地完成常见的界面开发、数据库应用等功能。然而,帮助的相对缺乏,使得许多组件的功能并

不为人们正确地使用,究其原因,仍然是认识上的问题。对于MIDAS开发中的核心部件,

TClientDataSet 和TDataSetProvider,由于资料的缺乏,人们在网上大多谈论的是李维的书籍内容

。我有幸在BDN上见到了Cary Jensen的Professional Developer系列文章,详细阐述了DELPHI的数

据库开发技术。现节选出其中的ClientDataSet部分,与大家共同分享。

ClientDataSet是一个功能强大的类,通过在内存中模拟表格,实现了其它数据集组件所不具备的强

大功能。以往只在Delphi和C++ Builder企业版中才提供这个组件,如今,Borland的全部产品(包

括最新的Kylix)都集成了TClientDataSet组件。

TClientDataSet从类的继承关系上来看,是TDataSet这个抽象类的子类,所以我们可以在TDataSet

这个抽象层次上对其进行我们熟悉的操作,比如导航、排序、过滤、编辑。要注意的是,

TClientDataSet使用了一种全新的技术,它将所有的数据均放在内存中,所以 TClientDataSet是个

只存在内存中的“虚拟表”,因此对数据库的操作是非常快的。在PIII 850,512MB的机器上对十万

条记录进行建索引的操作,花费的时间少于半分钟。

与一般的数据集组件不同,TClientDataSet使用的技术比较特别,本着高速度、低存储需求的原则

,TClientDataSet的内部使用了两个数据存储源。第一个是其Data属性,这是当前内存数据的视图

,反映了所有的数据改变。如果用户从数据中删除一条记录,则此记录将从Data中消失,相应地,

加入一条新记录后,此记录便存在Data属性中了。

另一个数据源是Delta属性,故名思义,即增量的意思,这个属性反映了对数据的改变。无论是向

Data属性新增还是删除记录,都会在Delta中记录下来,如果是修改了Data中的记录,则会在Delta

保存两条相应的记录,一条是原始记录,另一条仅包含修改的字段值。正因为Delta的存在和

TClientDataSet在内存中记录数据的特点,所有的改变都没有立即更新加对应的物理存储中,可以

根据这些信息在适当的时候恢复,所以 TClientDataSet天生具有缓冲更新功能。

为了使数据更新回数据存储源,我们要调用TClientDataSet中对应的方法。如果ClientDataSet与

DataSetProvider关联,那么仅需调用TClientDataSet的ApplyUpdates方法即可保存数据的更新,但

如果 TClientDataSet没有对应的TDataSetProvider存在,而是直接同文件关联,那么,这种方式是

非常有趣的,我们在 BriefCase模型中会再次讲解这个问题。此时,如果使用TClientDataSet的

SaveToFile和LoadFromFile,都会保留着Delta。调用MergeChangeLog和ClearChanges后,Delta的

内容才会被
清空。只是前者是将Delta的数据同Data结合起来,将改变存储到物理介质上,而ClearChanges则是

一股脑儿全部清空,将数据回复到原始状态。大部分的应用都是将TClientDataSet与

TDataSetProvider结合使用的。两者联合使用的行为反映了Borland的设计宗旨,就是要提供一个面

向分布式环境的思路。我们下面来慢慢解释。

当我们将TClientDataSet对象的Active属性设为True或者调用其Open方法后,ClientDataSet会向

DataSetProvider发送一个取数据包请求。于是DataSetProvider便会打开对应的数据集,将记录指

针指向第一条记录,然后从头到尾依次扫描。对于扫描到的每一条记录,都会将其编码成一个

variant数组,我们通常将它称之为数据包。完成扫描后,DataSetProvider 会关闭指向的数据集,

并将所有的这些数据包传递给ClientDataSet。在我提供的演示程序中,你可以清楚地看到这种行为

(毕竟眼见为实吗!)。程序主界面右边的DBGrid连接到一个指向数据库表的数据源,

DataSetProvider即指向此表。当选择了ClientDataSet | Load菜单项时,你可以看到表格的数据被

依次扫描,一旦到达最后一条记录,表格便会被关闭,右边的DBGrid被清空,而左边反映

ClientDataSet数据的DBGrid便出显示出内存中的数据来。由于这个过程会在DBGrid上反映出来,所

以不到1000条记录的取出时间中,大部分都浪费在屏幕的更新显示上了,你可以选择ClientDataSet

| View Table Loading来禁止显示,而达到加速的目的。

在上面的描述中,我们没有提到一个重要的环节,即数据包是如何还原成表格的。那是因为

DataSetProvider会将数据包中的元数据解码出来,根据元数据(我们可以理解为数据表的结构)便

可以构造出与物理数据表一模一样的内存虚拟表。但要注意的是,尽管DataSetProvider指向的数据

表可能有多个索引,但这些信息是不会放在数据包中的,换句话说,ClientDataSet当中的数据默认

情况下是无索引的。但因为ClientDataSet具有与TDataSet一致的行为,所以我们可以在此基础上根

据需要重建索引。

在ClientDataSet中的数据被修改后,可以提交给物理数据表持久化这此改变。这个工作便是由

DataSetProvider完成的。内部工作原理是:DataSetProvider创建一个TSQLResolver的实例,这个

实例会生成要在底层数据上执行更改的SQL语句。详细地说,就是对修改日志中的每一条被删除、插

入、更改记录生成对应的SQL语句。这个语句的生成也可以由用户控制,DataSetProvider的

UpdateMode属性和ClientDataSet中的ProviderFlags属性都对SQL语句的生成有影响。

当然,你也可以换一种方式,即采取同单机或C/S结构一样的数据直接操作机制,绕过SQL语句和缓

冲更新机制来修改数据库。只需将 ResolveToDataSet属性设为True,那么DataSetProvider在持久

化更新时便不会使用TSQLResolve,而是直接修改物理数据源。即定位到要删除的记录,调用删除语

句,定位到修改记录,调用修改语句。我们可以对演示程序稍加修改,观察此种行为。请将演示程

序中的 DataSetProvider的ResolveToDataSet属性由False改为True,运行。在界面中修改数据并且

保存,你将会看到右边的导航按钮会在瞬间变得可用。

更绝妙的是,Borland考虑到了应用的多样性,为我们提供了BeforeUpdateRecord事件,这样,当

DataSetProvider对每个修改日志的记录进行操作时,都会触发此事件,我们可以在此事件中加入自

己的处理,如“加密操作”、“商业敏感数据处理”等应用,从而极大地方便了程序员,让程序员

对于数据具有完全的控制能力。分布式环境的复杂性对数据的存取提出了更高的要求,所以使用事

务来保证数据的完整性和一致性是非常必要的,Borland考虑到了这一点,当调用ClientDataSet的

ApplyUpdates时,你可以传递一个整数值来指明可以容忍的错误数量。如果你的数据非常严格,则

可以传递0值,这样,DataSetProvider在应用修改时便会打开一个事务,如果遇到错误,便会回退

此事务,修改日志将保持原样,并且将出错的记录标记出来,最后会触发OnReconcileError事件。

如果传递了一个大于0的数,则当出现的错误数量小于此指定值时,事务会被提交,发生错误而导致

提交失败的记录会保留在Delta中,而提交成功的记录会从修改日志中删除。若错误数量达到指定值

,则事务会回退,结果同整数值为0的情况。如果值为负数,则会交所以可提交的数据都提交,不可

提交的数据仍然保存在修改日志中,并将出错记录标记出来。

虽然,Borland是为了满足分布式编程的需要而设计了TClientDataSet,但在其它类型的编程环境中

使用ClientDataSet也具有积极的意义。首先,我们可以看到,由于数据均在内存中进行操作,而且

仅在打开数据库取数据时和将修改持久到回数据库时,才有数据库开销,其它时间数据库为零,这

样就极大地增加了数据库的负荷,让数据库服务器能满足更多用户的连接请求。其次,

ClientDataSet具有其它数据集所不具备的许多高级功能,这为程序员进行复杂的编程提供了便利,

可以不考虑数据库本身是否支持这此功能,而让ClientDataSet去处理这些复杂而繁琐的细节。最后

,ClientDataSet在数据存储和应用程序间起到一个抽象层的作用。假如你的程序使用了

TClientDataSet,那么如果你以后要更改数据库存储机制。比如说由BDE移植到dbExpress,或者从

ADO移植到Interbase Express,你的用户界面和数据控制部分几乎就不用改变,只需要将

DataSetProvider指向新的数据存取组件即可。顺便说一句,由于缓冲更新的存在,用户可能非常厌

恶调用ApplyUpdates操作,那么你可以将此调用放入AfterPost和AfterDelte中,让用户的操作更方

便。





clientdataset的使用(二)

引自:

http://blog.sina.com.cn/s/blog_5407dd05010006h4.html~type=v5_one&label=rela_nextarticle

与TTable、TQuery一样,TClientDataSet也是从TDataSet继承下来的,它通常用于多层体系结构的

客户端。TClientDataSet最大的特点是它不依赖于BDE(Borland Database Engine),但它需要一个

动态链接库的支持,这个动态链接库叫DBCLIENT.DLL。在客户端,也不需要用TDatabase构件,因为

客户端并不直接连接数据库。
  由于TClientDataSet是从TDataSet继承下来的,所以,它支持诸如编辑、搜索、浏览、纠错、

过滤等功能。由于 TClientDataSet在内存中建立了数据的本地副本,上述操作的执行速度很快。也

正是由于TClientDataSet并不直接连接数据库,因此,客户程序必须提供获取数据的机制。在

Delphi 4中,TClientDataSet有三种途径获取数据:
.从文件中存取数据。
.从本地的另一个数据集中获取数据。
.通过IProvider接口从远程数据库服务器获取数据。
  在一个客户程序中,可以同时运用上述三种机制获取数据。
11.1 浏览和编辑数据
  和其他数据集构件一样,可以用标准的数据控件显示由TClientDataSet引入的数据集,当然,

这需要借助于TDataSource构件。
  由于TClientDataSet是从TDataSet继承下来的,所以,凡是其他数据集构件支持的功能,

TClientDataSet构件也大致具备。不同的是,TClientDataSet能够在内存中建立数据的副本,因此

,TClientDataSet比其他数据集构件增加了一些特殊的功能。
11.1.1 浏览数据
  可以用标准的数据控件显示由TClientDataSet引入的数据集。在运行期,可以调用诸如First、

GotoKey、Last、Next和Prior等函数来浏览数据。
  TClientDataSet也支持书签功能,可以用书签来标记某条记录,以后就可以方便地找到这条记

录。
  对于TTable、TQuery等数据集构件来说,只能读RecNo属性来判断当前记录的序号。对于

TClientDataSet构件来说,还可以写RecNo属性,使某一序号的记录成为当前记录。
11.1.2 CanModify属性
  TDataSet的CanModify属性用于判断数据集中的数据是否可以修改。CanModify属性本身是只读

的,也就是说,数据是否能够修改不取决于应用程序。
  不过,TClientDataSet构件有其特殊性,因为TClientDataSet已经把数据在内存中建立了副本

,因此,应用程序可以决定是否允许修改数据。如果不允许用户修改数据,只要把ReadOnly属性设

为True,此时,CanModify属性肯定返回False。
  与其他数据集构件不同,修改TClientDataSet构件的ReadOnly属性时,不需要事先把Active属

性设为True。
11.1.3 取消修改
  TClientDataSet传输数据的基本单位称为数据包,当前的数据包可以由Data属性来访问。不过

,用户对数据的修改并不直接反映到Data属性中,而是临时写到一个日志即Delta属性中,这样做的

好处是以后随时可以取消修改。
  不过,这里要说明一点,尽管用户的修改并没有反映到Data,当用户在数据控件中看到的却是

最新修改的数据。如果一条记录被反复修改了多次,用户看到的只是最新的数据,但日志中却记载

了多次。
  要取消上一次的修改,调用UndoLastChange函数。UndoLastChange需要传递一个布尔类型的参

数叫FollowChange,如果FollowChange参数设为True,光标就移到被恢复的记录上,如果

FollowChange参数设为False,光标仍然在当前记录上。
  ChangeCount属性返回日志中记载的修改次数。如果一条记录被反复修改了多次,每调用一次

UndoLastChange能够逐级取消上一次的修改。
  UndoLastChange只能取消上一次的修改,如果想一下子取消所有的修改,首先要选择一个记录

,然后调用RevertRecord。RevertRecord将从日志中取消所有对当前记录的修改。
  TClientDataSet还有一个SavePoint属性,它能把当前的编辑状态保存起来,以后随时可以返回

当时的状态。例如,可以这样保存当前的状态:
  BeforeChanges := ClientDataSet1.SavePoint;
  以后,可以这样来恢复当时的状态:
  ClientDataSet1.SavePoint := BeforeChanges;
  应用程序可以保存多处状态,可以恢复其中一个状态,不过,一旦某个状态被恢复,在其之后

的状态就无效。
  如果要一下子取消日志中记载的所有修改,可以调用CancelUpdates函数。CancelUpdates将把

日志清空,取消所有的修改。
  如果LogChanges属性设为False,用户对数据的修改就会直接反映到Data属性中。
11.1.4 合并修改
  要把日志中记载的修改合并到Data属性中,有两种方式,具体使用哪一种方式,取决于应用程

序获取数据的机制。不过,不管是哪种机制,合并后,日志自动被清空。
  对于一个从文件中获取数据的程序来说,只要调用MergeChangeLog函数,就把日志中记载的修

改合并到Data属性中。不用担心其他用户同时修改了数据。
  对于一个从应用服务器获取数据的程序来说,就不能调用MergeChangeLog来合并数据,而要调

用ApplyUpdates函数,ApplyUpdates会把日志中记载的修改传递给应用服务器,待应用服务器成功

地把数据更新了数据库服务器后,才会合并到Data属性中。
11.1.5 纠错
  TClientDataSet支持纠错功能。一般情况下,需要自己建立纠错规则,以便对用户输入的数据

进行纠错。
  此外,如果获得了IProvider接口的话,还可以从远程服务器引入纠错规则。
  有时候,客户端可能需要暂时禁止纠错,因为客户端从应用服务器检索数据是分阶段进行的,

在所有的数据检索完毕之前,有些纠错规则很可能会报错。
要暂时禁止纠错,可以调用DisableConstraints,要重新允许纠错,可以调用EnableConstraints函

数。DisableConstraints和EnableConstraints实际上都是作用于一个内部的计数。
11.2 索 引
  使用索引有这么几个好处:
.在数据集中定位记录比较快。
.能够在两个数据集之间建立Lookup或Master/Detail关系。
.可以对记录排序。
  在多层体系结构中,当客户程序从应用服务器检索数据时,它同时获得了默认的索引。默认的

索引叫DEFAULT_ORDER,可以使用这个索引排序,但不能修改或删除这个索引。
  除了默认的索引外,TClientDataSet还对日志中记载的记录自动建立了一个副索引叫

CHANGEINDEX。与DEFAULT_ORDER一样,不能修改或删除这个副索引。
  另外,还可以使用数据集中已建立的其他索引,或者自己建立索引。
11.2.1 创建一个新的索引
  要创建一个新的索引,可以调用AddIndex。AddIndex需要传递若干个参数:
  一是Name参数,用于指定索引名。在运行期切换索引时需要用到索引的名称。
  二是Fields参数,它是一个字符串,用于指定索引中的字段名,彼此之间用分号隔开。
  三是Options参数,用于设置索引的选项,包含ixDescending元素表示按降序排列,包含

ixCaseInsensitive元素表示大小写不敏感。
  四是DescFields参数,它也是一个字符串,用于指定若干个字段名,这些字段将按照降序排列


  五是CaseInsFields参数,它的作用与DescFields参数类似,包含在CaseInsFields参数中的字

段将对大小写不敏感。
  六是GroupingLevel参数,用于指定分组级别,其值不能超过索引中的字段数。
  下面的代码创建了一个索引:
If Edit1.Text <> '' and ClientDataSet1.Fields.FindField(Edit1.Text) then
Begin
ClientDataSet1.AddIndex(Edit1.Text+'Index',Edit1.Text,  
  [ixCaseInsensitive],'','',0);
ClientDataSet1.IndexName := Edit1.Text + 'Index';
End;
为了避免创建一个索引,可以临时用IndexFieldNames属性来指定若干个字段,让数据集按这些字段

排序。





clientdataset的使用(三)

引自:

http://blog.sina.com.cn/s/blog_5407dd05010006h5.html~type=v5_one&label=rela_nextarticle



11.2.2 删除和切换索引
  要删除一个先前创建的索引,可以调用DeleteIndex并指定要删除的索引名称。注意:

DEFAULT_ORDER和CHANGEINDEX不能删除。
  如果建立了多个索引,可以任意选择其中的一个索引,这就要用到IndexName属性。
11.2.3 用索引把数据分组
  选择了一个索引后,数据集将自动按其中的字段进行排序。这样,临近的记录往往在关键字段

上含有相同的值。例如,假设有一个表是这样的:
SalesRep Customer OrderNo Amount
1      1     5    100
1      1     2    50
1      2     3    200
1       2     6    75
2      1     1    10
2      3     4    200
  可以看出,SalesRep字段的值有重复的。对于SalesRep字段的值为1的来说,Customer字段的值

也有重复的。这就是说,可以按 SalesRep字段分组,进而再按Customer字段分组。显然,这里的分

组级别是不同的,按SalesRep字段建立的分组属于第一级,按 Customer字段建立的分组属于第二级

。实际上,分组级别取决于字段在索引中的顺序。
  TClientDataSet可以决定是否按照分组级别来显示记录的值。例如,也许想以下面这种形式显

示数据:
SalesRep Customer OrderNo Amount
1      1    5    100
           2    50
       2    3    200
           6    75
2      1    1    10
2      3    4    200
  要判断当前记录某一级的什么位置,可以调用GetGroupState函数。GetGroupState函数需要传

递一个参数,用于指定分组级别。
11.3 计 算 字 段
  与其他数据集一样,也可以在TClientDataSet建立的数据集中增加计算字段。计算字段的值是

基于同一个记录中的其他字段计算出来的。
  在其他数据集中,只要用户修改了数据或当前记录发生改变,就会触发OnCalcFields事件,换

句话说,计算字段的值就被计算一次。
  TClientDataSet引入了“内部计算字段”的概念。与一般的计算字段不同的是,内部计算字段

的值将随其他字段的值一起存取,这样,只有当用户修改了数据才会触发OnCalcFields事件,如果

仅仅改变了当前记录,不会触发OnCalcFields事件。也就是说,内部计算字段的值需要重新计算的

机会大大减少。
  在处理OnCalcFields事件的句柄中,首先要判断State属性。如果State属性返回

dsInternalCalc,此时需要计算内部计算字段的值。如果State属性返回dsCalcFields,此时需要计

算一般的计算字段的值。
11.4 统 计 值
  TClientDataSet增加了统计的功能,它可以基于分组自动计算总和、平均、计数、最大、最小

值。当用户编辑数据时,这些统计值会自动跟着变化。
11.4.1 指定统计方式
  要指定怎样进行统计,就要用到Aggregates属性。这个属性是一个TAggregates对象,它用于管

理一组TAggregate对象。
  在设计期,可以单击Aggregates属性边上的省略号按钮打开如图11.1所示
的编辑器。
  图11.1 管理一组TAggregate对象
  单击按钮可以增加一个TAggregate对象,单击按钮可以删减一个TAggregate对象,单击按钮可

以把TAggregate对象前移,单击按钮可以把TAggregate对象后移。
  可以用字段编辑器专门创建一个用于表达统计值的字段,该字段的类型必须是“Aggregate”。

Delphi 4会自动创建一个TAggregate对象,并加到Aggregates属性中。选择一个TAggregate对象,

Object Inpector将显示该对象的属性。
  其中,Expression属性用于指定统计表达式,例如:
Sum(Field1)
  也可以是比较复杂的表达式:
Sum(Qty * Price) - Sum(AmountPaid)
  在表达式中,可以使用下列统计运算符:
.Sum计算一组数据的总和。
.Avg计算一组数据的平均值。
.Count计算一组数据中的非空值的个数。
.Min计算一组数据的最小值。
.Max计算一组数据的最大值。
  除了上述几个统计运算符外,还可以使用过滤条件中所能使用的运算符,但不能嵌套。在一个

表达式中,可以混合出现几个统计值或常量,但不能混合出现统计值和字段。
  Sum(Qty * Price){合法}
  Max(Field1) - Max(Field2){合法}
  Avg(DiscountRate) * 100{合法}
  Min(Sum(Field1)){非法,不能嵌套}
  Count(Field1) - Field2{非法,统计值和字段不能混合出现在一个表达式中}
11.4.2 指定分组
  默认情况下,统计值是基于数据集中所有的记录计算出来的。不过,也可以针对一部分记录计

算统计值,这就需要事先建立分组。
  前面在介绍索引时已经提到分组的概念。可以通过IndexName属性和GroupingLevel属性来选择

使用哪个索引以及最大的分组级别。
  例如,假设有一个表是这样的:
SalesRep Customer OrderNo Amount
1      1     5    100
1      1     2    50
1      2     3    200
1       2     6    75
2      1     1    10
2      3     4    200
  如果要按SalesRep字段分组,并且指定其中的第一级,程序代码应当这样写:
Agg.Expression := 'Sum(Amount)';
Agg.IndexName := 'SalesCust';
Agg.GroupingLevel := 1;
Agg.AggregateName := 'Total for Rep';
11.4.3 怎样获取统计值
  要获取统计值,可以调用TAggregate对象的Value函数。如果统计值是基于数据集中所有的记录

计算出来的,随时可以调用Value函数。如果统计值是基于分组计算出来的,必须保证当前记录正好

位于该分组内。因此,在调用Value之前,最好先调用GetGroupState函数看看当前记录是否位于该

分组内。
  要在数据控件中显示统计值,必须事先在字段编辑器中创建一个永久字段对象,该字段的类型

必须是Aggregate。
11.5 数 据 包
  通过Data属性可以访问客户程序从应用服务器检索到的数据。程序示例如下:
Procedure TForm1.Button1Click(Sender: TObject);
Begin
ClientDataSet1.Data := ClientDataSet1.Provider.DataRequest(FilterEdit.Text);
End;
11.5.1 直接对Data属性赋值
  前面讲过,客户程序既可以通过IProvider接口获取数据,也可以从另一个数据集获取数据,后

者就是通过Data属性赋值的。程序示例如下:
  ClientDataSet1.Data := ClientDataSet2.Data;
  一旦Data被赋值,就可以用标准的数据控件显示这些数据。
  注意:当从另一个数据集获取数据时,另一个数据集的日志也将被复制过来,但不包括原来的

范围和过滤条件。
  如果要从另一个基于BDE的数据集中获取数据,可以通过数据集构件的Provider属性,程序示例

如下:
  ClientDataSet1.Data := Table1.Provider.Data;
  如果要从一个自定义的数据集获取数据,首先要创建一个临时的TProvider构件,然后设置其

DataSet属性指定这个自定义的数据集。程序示例如下:
TempProvider := TDataSetProvider.Create(Form1);
TempProvider.DataSet := SourceDataSet;
ClientDataSet1.Data := TempProvider.Data;
TempProvider.Free;
11.5.2 在数据包中加入自定义的信息
  可以把自定义的信息加到数据包中。当把数据保存到文件或流中时,这些自定义的信息也将保

存到文件或流中。如果把数据包直接赋值给另一个数据集的话,这些自定义的信息也将被复制。
  要把自定义的信息加到数据包中,可以调用SetOptionalParam函数。要从数据包中检索自定义

的信息,可以调用GetOptionalParam。程序示例如下:
Procedure TAppServer.Provider1UpdateData(Sender: TObject; DataSet: TClientDataSet);
var
WhenProvided: TDateTime;
Begin
WhenProvided := DataSet.GetOptionalParam('TimeProvided');
...
End;
11.5.3 克隆另一个数据集
  调用TClientDataSet的CloneCursor函数可以获得一个数据集的完全相同的副本。它与直接通过

Data属性赋值是有区别的。
  区别之一:数据在两个数据集之间是共享的,修改其中一个将同时修改另一个。
  区别之二:除了数据外,CloneCursor函数还复制了一些属性和事件,这取决于Reset和

KeepSettings参数怎样设置。
   CloneCursor函数需要传递三个参数,其中,Source参数指定源数据集,Reset参数和

KeepSettings参数用于设置除了数据外是否还要复制下列属性和事件:Filter、Filtered、

FilterOptions、OnFilterRecord、IndexName、 MasterSource、MasterFields、ReadOnly、

RemoteServer、ProviderName、Provider。
  如果Reset和KeepSettings参数都设为False,源数据集的上述属性和事件都将被复制给目标数

据集。如果Reset参数设为 True,目标数据集的上述属性和事件都将被清空。如果Reset参数设为

False,而KeepSettings参数设为True,目标数据集的上述属性和事件不变,不过,必须保证这些属

性和事件与克隆后的数据相容。
11.6 与应用服务器通讯
  在多层体系结构中,客户程序通过IProvider接口与应用服务器交换数据。这一章介绍怎样在客

户端获得IProvider接口、怎样向应用服务器传递参数、怎样向应用服务器请求数据、怎样把用户对

数据的修改写到数据库中。
11.6.1 怎样在客户端获得IProvider接口
  在单层应用程序以及工作在“公文包”模式下的多层应用程序中,不需要用到IProvider接口。

而在多层体系结构中,客户程序要与应用服务器交换数据,首先必须获得IProvider接口,这就要用

到RemoteServer属性和ProviderName属性。
  RemoteServer属性用于指定客户端的MIDAS连接构件。MIDAS连接构件又称Data Broker,用于建

立和维护与应用服务器的连接。
  在设计期,正确设置了RemoteServer属性后,就可以在对象观察器中为ProviderName属性选择

一个值,实际上就是选择应用服务器上的一个TProvider构件。
11.6.2 向应用服务器传递参数
  客户程序可以向应用服务器传递参数,这些参数实际上是传递给应用服务器上的TQuery构件或

TStoredProc构件。既可以在设计期也可以在运行期设置参数。
  在设计期,可以单击Params属性边上的省略号按钮,打开一个如图11.2所示的编辑器。
  图11.2 设置参数
  单击按钮可以增加一个参数,单击按钮可以删减一个参数,单击按钮可以把一个参数前移,单

击按钮可以把一个参数后移。
  选择一个参数,对象观察器将显示该参数(TParam对象)的属性。
  在运行期可以调用TParams的CreateParam函数来创建一个参数。例如,下面的代码创建了一个

参数叫CustNo,它的使用类型是ptInput,数据类型是ftInteger,它的值设为605。
With ClientDataSet1.Params.CreateParam(ftInteger, 'CustNo', ptInput) Do
AsInteger := 605;
  设置好参数以后,如果TClientDataset的Active属性是False,只要把Active属性设为True,这

些参数将被自动传递给应用服务器。如果Active属性已经为True,就要调用SendParams函数把参数

传递给应用服务器。
  注意:传递给应用服务器的参数必须与TQuery构件或TStoredProc构件的参数匹配,包括名称、

数据类型和参数类型。
11.6.3 怎样向应用服务器请求数据
  TClientDataSet提供了两个属性和三个方法,用于怎样向应用服务器请求数据:
  一是FetchOnDemand属性。如果这个属性设为True,TClientDataSet会根据需要自动检索附加的

数据包,例如BLOB字段的值或者嵌套表的内容。如果这个属性设为False,程序需要显式地调用

GetNextPacket才能获得这些附加的数据包。
  二是PacketRecords属性,用于设置一个数据包中最多可容纳的记录数,设为-1表示一个数据包

可以容纳数据集的所有记录。
  三是GetNextPacket函数,用于向应用服务器检索下一个数据包,并把检索到的数据包添加到前

一次检索到的数据包的后面。这个函数返回实际检索到的记录数。
  四是FetchBlobs过程,用于从应用服务器检索BLOB字段的值。如果FetchOnDemand属性设为True

,就没必要调用FetchBlobs函数。
  五是FetchDetails过程,用于检索嵌套表中的数据。如果FetchOnDemand属性设为True,就没必

要调用FetchDetails函数。
11.6.4 更新数据库
  在多层体系结构中,用户在客户端修改了数据后,需要把最新的数据写到数据库中,这就要调

用TClientDataSet的ApplyUpdates函数。
  ApplyUpdates只需要传递一个参数叫MaxErrors,用于指定一个整数,当遇到无法更新的记录超

过这个数时,此次更新就中止。如果 MaxErrors参数设为0,表示只要遇到一个错误更新就中止,客

户端的日志保持不变。如果MaxErrors参数设为-1,当应用服务器发现有错误的记录,就尝试更新下

一个记录,等所有的记录都尝试过以后才返回。
  ApplyUpdates会自动调用Reconcile函数,进而调用应用服务器上的TProvider构件的

ApplyUpdates函数去更新远程的数据库服务器。没有被DBMS服务器认可的记录通过Reconcile返回给

客户端,此时将在客户端触发OnReconcileError事件让您更正错误。最后,ApplyUpdates函数返回

仍然没有被认可的记录数。
11.7 在文件中存取数据
  要从文件中读取数据,可以调用LoadFromFile函数。LoadFromFile函数需要传递一个参数,用

于指定文件名。文件名应包含完整的路径。如果客户程序总是从一个固定的文件中读取数据,可以

设置FileName属性指定一个文件名,以后,当TClientDataSet引入的数据集打开时,就自动从这个

文件中读取数据,不需要调用LoadFromFile。
  要从流中读取数据,可以调用LoadFromStream。LoadFromStream需要传递一个参数,用于指定

一个流对象。
  注意:LoadFromFile(LoadFromStream)只能从先前用SaveToFile(SaveToStream)保存的文件中

读取数据。
  要把数据保存到文件中,可以调用SaveToFile函数。SaveToFile需要传递一个参数,用于指定

文件名。如果指定的文件已存在,文件中的数据将被覆盖。如果客户程序总是把数据保存到一个固

定的文件中,可以设置FileName属性指定一个文件名,当TClientDataSet引入的数据集关闭时,就

自动把数据保存到这个文件中,不需要调用SaveToFile。
  要把数据保存到流中,可以调用SaveToStream。SaveToStream需要传递一个参数,指定一个流

对象。
  注意:当把数据保存到文件或流中时,日志中记载的修改仍然保留。这样,当下次调用

LoadFromFile或LoadFromStream读取数据时,仍然可以恢复原来的数据