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的 内容才会被 一股脑儿全部清空,将数据回复到原始状态。大部分的应用都是将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在内存中建立了数据的本地副本,上述操作的执行速度很快。也 正是由于TClientDataSet并不直接连接数据库,因此,客户程序必须提供获取数据的机制。在 Delphi 4中,TClientDataSet有三种途径获取数据: 这需要借助于TDataSource构件。 TClientDataSet构件也大致具备。不同的是,TClientDataSet能够在内存中建立数据的副本,因此 ,TClientDataSet比其他数据集构件增加了一些特殊的功能。 GotoKey、Last、Next和Prior等函数来浏览数据。 录。 TClientDataSet构件来说,还可以写RecNo属性,使某一序号的记录成为当前记录。 的,也就是说,数据是否能够修改不取决于应用程序。 ,因此,应用程序可以决定是否允许修改数据。如果不允许用户修改数据,只要把ReadOnly属性设 为True,此时,CanModify属性肯定返回False。 性设为True。 ,用户对数据的修改并不直接反映到Data属性中,而是临时写到一个日志即Delta属性中,这样做的 好处是以后随时可以取消修改。 最新修改的数据。如果一条记录被反复修改了多次,用户看到的只是最新的数据,但日志中却记载 了多次。 数叫FollowChange,如果FollowChange参数设为True,光标就移到被恢复的记录上,如果 FollowChange参数设为False,光标仍然在当前记录上。 UndoLastChange能够逐级取消上一次的修改。 ,然后调用RevertRecord。RevertRecord将从日志中取消所有对当前记录的修改。 当时的状态。例如,可以这样保存当前的状态: 的状态就无效。 日志清空,取消所有的修改。 序获取数据的机制。不过,不管是哪种机制,合并后,日志自动被清空。 改合并到Data属性中。不用担心其他用户同时修改了数据。 用ApplyUpdates函数,ApplyUpdates会把日志中记载的修改传递给应用服务器,待应用服务器成功 地把数据更新了数据库服务器后,才会合并到Data属性中。 进行纠错。 在所有的数据检索完毕之前,有些纠错规则很可能会报错。 数。DisableConstraints和EnableConstraints实际上都是作用于一个内部的计数。 索引叫DEFAULT_ORDER,可以使用这个索引排序,但不能修改或删除这个索引。 CHANGEINDEX。与DEFAULT_ORDER一样,不能修改或删除这个副索引。 ixCaseInsensitive元素表示大小写不敏感。 。 段将对大小写不敏感。 排序。 clientdataset的使用(三) 引自: http://blog.sina.com.cn/s/blog_5407dd05010006h5.html~type=v5_one&label=rela_nextarticle 11.2.2 删除和切换索引 DEFAULT_ORDER和CHANGEINDEX不能删除。 上含有相同的值。例如,假设有一个表是这样的: 也有重复的。这就是说,可以按 SalesRep字段分组,进而再按Customer字段分组。显然,这里的分 组级别是不同的,按SalesRep字段建立的分组属于第一级,按 Customer字段建立的分组属于第二级 。实际上,分组级别取决于字段在索引中的顺序。 示数据: 递一个参数,用于指定分组级别。 基于同一个记录中的其他字段计算出来的。 句话说,计算字段的值就被计算一次。 的值将随其他字段的值一起存取,这样,只有当用户修改了数据才会触发OnCalcFields事件,如果 仅仅改变了当前记录,不会触发OnCalcFields事件。也就是说,内部计算字段的值需要重新计算的 机会大大减少。 dsInternalCalc,此时需要计算内部计算字段的值。如果State属性返回dsCalcFields,此时需要计 算一般的计算字段的值。 值。当用户编辑数据时,这些统计值会自动跟着变化。 理一组TAggregate对象。 以把TAggregate对象前移,单击按钮可以把TAggregate对象后移。 Delphi 4会自动创建一个TAggregate对象,并加到Aggregates属性中。选择一个TAggregate对象, Object Inpector将显示该对象的属性。 表达式中,可以混合出现几个统计值或常量,但不能混合出现统计值和字段。 算统计值,这就需要事先建立分组。 使用哪个索引以及最大的分组级别。 计算出来的,随时可以调用Value函数。如果统计值是基于分组计算出来的,必须保证当前记录正好 位于该分组内。因此,在调用Value之前,最好先调用GetGroupState函数看看当前记录是否位于该 分组内。 必须是Aggregate。 者就是通过Data属性赋值的。程序示例如下: 范围和过滤条件。 如下: DataSet属性指定这个自定义的数据集。程序示例如下: 存到文件或流中。如果把数据包直接赋值给另一个数据集的话,这些自定义的信息也将被复制。 的信息,可以调用GetOptionalParam。程序示例如下: Data属性赋值是有区别的。 KeepSettings参数怎样设置。 KeepSettings参数用于设置除了数据外是否还要复制下列属性和事件:Filter、Filtered、 FilterOptions、OnFilterRecord、IndexName、 MasterSource、MasterFields、ReadOnly、 RemoteServer、ProviderName、Provider。 据集。如果Reset参数设为 True,目标数据集的上述属性和事件都将被清空。如果Reset参数设为 False,而KeepSettings参数设为True,目标数据集的上述属性和事件不变,不过,必须保证这些属 性和事件与克隆后的数据相容。 户端获得IProvider接口、怎样向应用服务器传递参数、怎样向应用服务器请求数据、怎样把用户对 数据的修改写到数据库中。 而在多层体系结构中,客户程序要与应用服务器交换数据,首先必须获得IProvider接口,这就要用 到RemoteServer属性和ProviderName属性。 立和维护与应用服务器的连接。 一个值,实际上就是选择应用服务器上的一个TProvider构件。 TStoredProc构件。既可以在设计期也可以在运行期设置参数。 击按钮可以把一个参数后移。 参数叫CustNo,它的使用类型是ptInput,数据类型是ftInteger,它的值设为605。 些参数将被自动传递给应用服务器。如果Active属性已经为True,就要调用SendParams函数把参数 传递给应用服务器。 数据类型和参数类型。 数据包,例如BLOB字段的值或者嵌套表的内容。如果这个属性设为False,程序需要显式地调用 GetNextPacket才能获得这些附加的数据包。 可以容纳数据集的所有记录。 一次检索到的数据包的后面。这个函数返回实际检索到的记录数。 ,就没必要调用FetchBlobs函数。 要调用FetchDetails函数。 用TClientDataSet的ApplyUpdates函数。 过这个数时,此次更新就中止。如果 MaxErrors参数设为0,表示只要遇到一个错误更新就中止,客 户端的日志保持不变。如果MaxErrors参数设为-1,当应用服务器发现有错误的记录,就尝试更新下 一个记录,等所有的记录都尝试过以后才返回。 ApplyUpdates函数去更新远程的数据库服务器。没有被DBMS服务器认可的记录通过Reconcile返回给 客户端,此时将在客户端触发OnReconcileError事件让您更正错误。最后,ApplyUpdates函数返回 仍然没有被认可的记录数。 于指定文件名。文件名应包含完整的路径。如果客户程序总是从一个固定的文件中读取数据,可以 设置FileName属性指定一个文件名,以后,当TClientDataSet引入的数据集打开时,就自动从这个 文件中读取数据,不需要调用LoadFromFile。 一个流对象。 读取数据。 文件名。如果指定的文件已存在,文件中的数据将被覆盖。如果客户程序总是把数据保存到一个固 定的文件中,可以设置FileName属性指定一个文件名,当TClientDataSet引入的数据集关闭时,就 自动把数据保存到这个文件中,不需要调用SaveToFile。 对象。 LoadFromFile或LoadFromStream读取数据时,仍然可以恢复原来的数据 |
原文地址:https://www.cnblogs.com/jijm123/p/11371747.html