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中,让用户的操作更方便。
分享到:
相关推荐
DELPHI数据控件TclientDataSet的详细说明 TclientDataSet是DELPHI数据控件中的一种重要组件,它继承自TDataSet,用于多层体系结构的客户端。与TTable、TQuery一样,TClientDataSet也可以用于浏览、编辑、搜索、纠错...
### TClientDataSet 用法详解 #### 一、TClientDataSet 概述 TClientDataSet是一种数据集组件,它从TDataSet继承而来,主要用于多层架构应用中的客户端部分。相较于其他数据集组件如TTable和TQuery,...
### TClientDataSet 控件学习知识点详解 #### 一、TClientDataSet 概览 TClientDataSet 是 Delphi 或其他 RAD Studio 系统中一个重要的数据处理控件,它继承自 TDataSet,主要用于本地数据存储和操作。相比于其他...
`TClientDataSet`的`Delta`属性在其中起到了关键作用。`TClientDataSet`是一个非常强大的组件,它不仅可以存储和处理数据,还能记录对数据的修改,这些修改就保存在`Delta`属性中。在三层架构中,通过合理利用`Delta...
cds-memleak 完成该项目的目的是揭示Delphi中一个广泛使用的名为TClientDataset的组件中的内存泄漏。动机我工作的公司到处都使用TClientDatasets。 我们已经编写了许多后端工具来支持我们的数据传递系统。 一种这样...
在Delphi编程环境中,内存表(TClientDataSet)是一种非常实用的数据处理工具,它可以在内存中存储和操作数据,无需连接到数据库服务器。利用TClientDataSet制作主从表是数据库应用设计中的常见方法,可以有效地管理...
主表的TClientDataSet通过设置其MasterSource和MasterField属性与从表的TClientDataSet关联。这样,当主表数据发生变化时,从表会自动更新。 此外,为了用户友好,我们还需要考虑数据导航和编辑功能。这可能涉及到...
在本教程中,我们将深入探讨如何将TClientDataSet的数据转换为JSON格式,以及如何将JSON数据恢复到TClientDataSet中,以实现与JSON的无缝对接。 首先,我们需要理解TClientDataSet的基本工作原理。TClientDataSet是...
能不能使用TClientDataSet又不用MIDAS.DLL呢? 很简单,就是uses一下MidasLib单元! MidasLib单元在Delphi6以及DELPHI的更高版本中才有,是Lib目录下的一个dcu文件。一旦在你的源程序中引用了MidasLib单元,...
TFDMemTable是FireMonkey框架下的一种内存数据表组件,类似于VCL中的TClientDataSet。它允许开发者在内存中创建和管理数据表,支持数据的增删改查操作,同时可以与数据库进行交互。TFDMemTable没有CDS的中间层特性,...
本资源"Delphi_database.rar"是一个关于Delphi数据库操作的示例程序,主要关注如何在Delphi中实现数据库的浏览功能。 在Delphi中,数据库访问通常依赖于数据存取组件(Data Access Components,简称DAC)。这些组件...
在Delphi中,元数据通常由TClientDataSet组件处理,它可以获取数据库表的结构信息,并在内存中创建一个数据集副本。通过调用TClientDataSet的CreateDataset方法,可以连接到数据库并加载元数据。例如: ```delphi ...
- **链接`TDataSource`**:创建`TDataSource`组件,并将其`DataSet`属性设置为`TClientDataSet`,这样`TDataSource`就能从`TClientDataSet`获取数据。 - **绑定`TDBGrid`**:将`TDBGrid`的`DataSource`属性设置为...
通过绑定到数据集组件(如TClientDataSet),DBGridEh可以实时显示和编辑数据库中的数据,支持多种数据类型,包括字符串、数字、日期/时间以及自定义对象。 在功能上,DBGridEh提供了许多高级特性。它支持列的定制...
标题提到的"Delphi Json 存 DataSet (memory)实用代码"是指如何将接收到的JSON数据转换并存储到内存中的DataSet(通常是TClientDataSet或TDataset的子类)中,以便进行进一步的处理或操作。描述中指出,这种做法...
以下是一些关于 CDS 的关键知识点: 1. **字段定义**:CDS 可以动态或静态定义字段。静态定义是在设计时设置,而动态定义则在运行时根据需要创建。 2. **数据加载**:CDS 可以从多种数据源加载数据,如 XML 文件、...
- **设置ListSource**:LookUpEdit的ListSource应设置为TClientDataSet,这样它的下拉列表就会显示TClientDataSet中的数据。 - **设置ListFields**:定义LookUpEdit的显示字段,这通常与LookupField相同。 - **...