Bootstrap

Delphi TClientDataSet的使用技巧

1. TClientDataSet、TDataSetProvider的使用技巧

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

TClientDataSet从类的继承关系上来看,是TDataSet这个抽象类的子类,所以我们可以在TDataSet这个抽象层次上对其进行我们熟悉的操作,比如导航、排序、过滤、编辑。要注意的是,TClientDataSet使用了一种全新的技术,它将所有的数据均放在内存中,所以TClientDataSet是个只存在内存中的“虚拟表”,因此对数据库的操作是非常快的。在PIII 850,512MB的机器上对十万条记录进行建索引的操作,花费的时间少于半分钟。

与一般的数据集组件不同,TClientDataSet使用的技术比较特别,本着高速度、低存储需求的原则,TClientDataSet的内部使用了两个数据存储源。第一个是其Data属性,这是当前内存数据的视图,反映了所有的数据改变。如果用户从数据中删除一条记录,则此记录将从Data中消失,相应地,加入一条新记录后,此记录便存在Data属性中了。

另一个数据源是Delta属性,故名思义,即增量的意思,这个属性反映了对数据的改变。无论是向Data属性新增还是删除记录,都会在Delta中记录下来,如果是修改了Data中的记录,则会在Delta保存两条相应的记录,一条是原始记录,另一条仅包含修改的字段值。正因为Delta的存在和TClientDataSet在内存中记录数据的特点,所有的改变都没有立即更新加对应的物理存储中,可以根据这些信息在适当的时候恢复,所以TClientDataSet天生具有缓冲更新功能。

 

问:       在发布用Delphi开发的数据库程序时,除了要安装应用程序之外,还需要同时发布数据库驱动程序。这对于一些只涉及单个或多个简单表数据存储的单机应用程序来说,就显得有点头重脚轻的感觉了。况且,有些应用程序本身需要存储大量数据,但本身又要求结果短小精悍的话,用Delphi常规开发数据库的方法就不能满足需要了。

答:       TClientDataSet控件继承自TDataSet,其数据存储文件格式扩展名为.cds,是基于文件型数据存储和操作的控件。该控件封装了对数据进行操作处理的接口和功能,而本身并不依赖上述几种数据库驱动程序。

1).FieldDefs: 字段定义列表属性

  开发者可通过单击属性编辑器中该属性编辑按钮,或在该控件上单击右键选择弹出菜单中的”Fields Editor”菜单进行字段编辑。设置完此属性后,实际上就相当于定义了表的结构;如果想装入已有的数据表的结构和数据,可通过单击右键选择弹出菜单中的”Assign Local Data”菜单,从弹出对话框中选取当前窗体中已与数据库连接好的数据集控件名称即可(当前窗体中必须已放置好要套用的数据集控件并打开激活)。

  

使用注意:

对于自定义的字段名表,该属性编辑完后,该控件仍然无法打开。必须右键单击该控件,选择弹出菜单中的”Create DataSet”菜单,让该控件以上述编辑的字段列表为依据,创建数据集后,才能够被激活打开和使用。否则,会出现类似”ClientDataSet1: Missing data provider or data packet.”的错误(包括在运行期,运行期可调用该控件的CreateDataSet方法,从而动态定义字段和表)。

 

2).FileName属性  说明:数据存储文件的名称。

  因该控件是基于文件型的数据操作控件,因此,必须指定所操作的数据文件名称(默认扩展名称.cds),从而打开和激活该控件,进而进行数据编辑。

 

例1:利用此属性打开指定的.cds文件

  var     Path: string;

  begin

     Path := ExtractFilePath(Application.ExeName); //取得可执行文件路径

     CDataSet1.FileName := Path + ’test.cds’;    CDataSet1.Open;

  end;

 

3).CreateDataSet方法  说明:该方法以FieldDefs中的字段名表为结构建立数据集,常用来进行动态定义表。

 

例2:动态创建一具有姓名和年龄两个字段的数据集。

  //――――创建字段名表―――――

  CDataSet.FieldDefs.Clear;

  with CDataSet.FieldDefs.AddFieldDef do

  begin

     Name := ’Name’;      Size := 10;   DataType := ftString;

  end;

  with CDataSet.FieldDefs.AddFieldDef do

  begin

     Name := ’Age’;     DataType := ftInteger;

  end;

  CDataSet.CreateDataSet;      //动态创建数据集

  CDataSet.Open;            //激活和打开该数据集

 

4).Open方法  说明: 打开和激活数据集控件,从而进行数据编辑。

         a. 如果指定了FileName属性,则直接用Open方法即可打开和激活该控件,见例1。

         b. 如果未指定FileName属性,可使用例2方法动态创建和打开数据集,进而操作数据。

 

5).LoadFromFile和SaveToFile      说明:从文件中装入表结构和数据以及存储数据到文件。该方法类似于Word中的打开新文件和另存为的功能。

 

  例3:将数据集的数据存储到指定文件中

             CDataSet.SaveToFile(’c:\windows\desktop\test.cds’);

 

6).First(到首),Prior(向前),Next(向后),Last(到尾),Edit(编辑),CanCel(取消编辑),Post(保存),Insert(插入记录),Append(添加记录),Delete(删除),Refresh(数据刷新)等数据集常用方法

 

  说明:当指定了FileName属性时,其Post方法可将数据存入指定的文件中,类似其SaveToFile方法;如果未指定存储文件名,则Post方法只将数据存储在RAM中。其它方法,同一般数据集控件使用方法,略。

 

7).Filter, Filtered: 过滤筛选属性  说明:用于筛选指定条件的记录,用法同一般数据集控件,略。

 

  例4:在已经激活打开的数据集中筛选性别为男性的记录

 

  CDataSet.Close;

  CDataSet.Filter := ’性别=’’’ + ’男’ + ’’’’;

  CDataSet.Filtered := True;

  CDataSet.Open;

 

2.使用TClientDataSet控件的应用程序发布的注意事项:

  如前所述,使用TClientDataSet控件的程序发布时不需要任何数据库驱动程序,大大节省了安装文件的大小。但是,在发布程序时别忘了将Windows系统目录下midas.dll(257KB)与应用程序一起发布(运行必须),否则,程序仍然无法正常运行。

 

//============================================================================

DataSetProvider的Option属性

看看远端数据模块里的DataSetProvider的Option属性是否设置正确!Provider控件的Options属性值是一个集合,用于设置有关打包和传递的选项,像显示哪些字段、允许哪种更新方式等等。

1.       poFetchBlobsOnDemand:若包含此项,则表示数据包中不包括BLOB字段。不过,如果客户端的TclientDataSet控件的FetchOnDemand属性设为True,那么客户端还是能自动地请求这些数据值;否则,客户端应用程序必须使用客户数据集的FetchBlobs方法来检索BOLB数据。

2.       poFetchDetailsOnDemand: 当用嵌套表的方式处理Master/Detail关系时,这里的Provider表示主/明细表中的主表。若包含此项,则Detail表中的字段将不会放入包中。不过,如果客户端的TclientDataSet控件的FetchOnDemand属性为True;那么客户端还是能自动的请求这些数据;否则,要显式调用FetchDetails方法。

3.       poIncFieldProps:若包含此项,则表示数据包中将包含下列字段属性:Alignment、MinValue、DisplayLabel、DisplayWidth、Visible、DidplayFormat、MaxValue、EditFormat、Currency、EditMask、DisplayValues等。

4.       poCascadeDeletes:当用嵌套表的方式处理Master/Detail关系时,这里的Provider表示主/明细表中的主表。若包含此项,则当主表中的记录被删除时,明细表中相应的记录将自动地被删除。

注意,要使用这个选项,数据库服务器需要建立参照完整性:也就是,在数据库中对主细表建立主外键关系,并在“关系”页选中“级联更新相关字段”、“级联删除相关记录”。

5.      poCascadeUpdates:当用嵌套表的方式处理Master/Detail关系时,这里的Provider表示主/明细表中的主表。若包含此项,则当主表中主键字段的值改变时,明细表中相应的记录将自动地被更新。

注意:要使用这个选项,数据库服务器需要建立参照完整性。参照4。

6.      poReadOnly:若包含此项,则表示不允许客户端数据集向Provider申请更新数据。

7.      poAllowMultiRecordUpdates:表示一个单一的更新将同时更新关联的许多表的记录,这有可能是通过触发、参照完整性或自定义的SQL语句来实现的。

8.      poDisableEdits:若包含此项,则表示不允许客户端更新已经存在的数据值,否则,将触发异常。

9.      poDisableInserts:若包含此项,则表示不允许客户端插入一个新的记录,否则,将触发异常。

10.     poNoReset:若包含此项,则表示在提供数据前,不允许客户端将光标指定在第一条记录。

11.     poAutoRefresh:若包含此项,则表示Provider将用当前的记录刷新客户端的数据集,而不管它是否已经申请更新。(这个功能好像并没有实现)

12.     poPropogateChanges:若包含此项,则表示服务器对记录的更新将返回给客户端并反映到客户端数据集中。

13.     poAllowCommandText:若包含此项,则表示客户端可以重载相关数据集的SQL语句、表的名字或存储过程。

 

采取同单机或C/S结构一样的数据直接操作机制,绕过SQL语句和缓冲更新机制来修改数据库。只需将ResolveToDataSet属性设为True,那么DataSetProvider在持久化更新时便不会使用TSQLResolve,而是直接修改物理数据源。即定位到要删除的记录,调用删除语句,定位到修改记录。请将演示程序中的DataSetProvider的ResolveToDataSet属性由False改为True,运行。在界面中修改数据并且保存,你将会看到右边的导航按钮会在瞬间变得可用。

 

更绝妙的是,Borland为我们提供了BeforeUpdateRecord事件,这样,当DataSetProvider对每个修改日志的记录进行操作时,都会触发此事件,我们可以在此事件中加入自己的处理,如“加密操作”、“商业敏感数据处理”等应用,从而极大地方便了程序员,让程序员对于数据具有完全的控制能力。

当调用ClientDataSet的ApplyUpdates时,你可以传递一个整数值来指明可以容忍的错误数量。如果你的数据非常严格,则可以传递0值,这样,DataSetProvider在应用修改时便会打开一个事务,如果遇到错误,便会回退此事务,修改日志将保持原样,并且将出错的记录标记出来,最后会触发OnReconcileError事件。如果传递了一个大于0的数,则当出现的错误数量小于此指定值时,事务会被提交,发生错误而导致提交失败的记录会保留在Delta中,而提交成功的记录会从修改日志中删除。若错误数量达到指定值,则事务会回退,结果同整数值为0的情况。如果值为负数,则会交所以可提交的数据都提交,不可提交的数据仍然保存在修改日志中,并将出错记录标记出来。

 

//============================================================================

2. TClientDataSet应用

 

1.动态索引

procedure TForm1.DBGrid1TitleClick(Column: TColumn);

begin

if (not column.Field is Tblobfield) then                            //Tblobfield不能索引,二进制

ClientDataSet1.IndexFieldNames:=column.Field.FieldName;

end;

 

2.多层结构中主从表的实现

设主表ClientDataSet1.packetrecord为-1, 所有记录

设从表ClientDataSet1.packetrecord为0, 当前记录

 

3.Taggregates使用

(1)在字段编辑中add new field类型为aggregates

     后设置expression(表达试)

     设置active:=true即可

     使用dbedit的field为前者即可

(2)使用Aggergates属性add设计表达试

    调用

  showmessage(floattostr(ClientDataSet1.Aggregates.Count));

  showmessage(ClientDataSet1.Aggregates.Items[0].Value);

 

4.在单层数据库中不要BDE

  使用ClientDataSet代替table,使用ClientDataSet的loadfilename装入cds

  代替table的tablename的db或者dbf

  原来的程序改造方法:

      加一个ClientDataSet,使用右键assign locate data

      后savetofile,再loadfromfile,后删除table

      将原连table的datasource设为ClientDataSet

  唯一注意的是:要将midas.dll拷到system或者当前目录

 

5.三层结构的公文包的实现方法

      同时设定1:filename(*.cds)2.remote server

 

6.可以对data赋值(从另一个数据集取值)

      ClientDataSet2.Data:=ClientDataSet1.Data;

      ClientDataSet2.Open;

 或者

      ClientDataSet2.CloneCursor(ClientDataSet1,true);

      ClientDataSet2.Open;

 

7.附加数据取得

  客户程序向应用服务器请求数据。如果TClientDataSet 的FetchOnDemand 属性设为True,

  客户程序会根据需要自动检索附加的数据包如BLOB字段的值或嵌套表的内容。

  否则,

  客户程序需要显式地调用GetNextPacket 才能获得这些附加的数据包。

  ClientDataSet的packetrecords设置一次取得的记录个数

 

8.ClientDataSet与服务器端query连接方法

  (1)sql内容为空

     ClientDataSet1.Close;

     ClientDataSet1.CommandText:=edit1.Text;        //即sql内容

     ClientDataSet1.Open;

   对于没有应用服务器设置filter 如:country like 'A%'             filtered=true可实现sql功能

  (2)有参数

   如服务端query的sql为select * from animals where name like :dd

   则:客户端ClientDataSet

   var   pm : Tparam;

  begin

     ClientDataSet1.Close;             ClientDataSet1.ProviderName:='DataSetProvider1';

     pm:=Tparam.Create(nil);        pm.Name:='dd';     pm.DataType:=ftString;

 

ClientDataSet1.Params.Clear;     ClientDataSet1.Params.AddParam(pm);

     ClientDataSet1.Params.ParamByName('dd').AsString:=edit1.Text ;

     ClientDataSet1.Open;

     pm.Free;

  end;

 

9.数据的更新管理

  (1)      savepoint 保存目前为止数据状态,可以恢复到这个状态

  var    pp   :      integer;

  begin

     pp:=ClientDataSet1.SavePoint;

     ClientDataSet1.Edit;

     ClientDataSet1.FieldByName('姓名').asstring:='古话';

     ClientDataSet1.Post;

     table1.Refresh;

   end;

  恢复点    ClientDataSet1.SavePoint:=pp;

  (2)  cancel    ,     RevertRecord

       取消对当前记录的修改,只适合没有post的,如果post,调用   RevertRecord

   (3)     cancelupdate    取消对数据库所有的修改

  (4)  UndoLastChange(boolean)  ,      changecount    取消上一次的修改,可以实现连续撤消

           参数为true:光标到恢复处          false:光标在当前位置不动

            Changecount   返回修改记录的次数,一个记录修改多次,返回只一次

                                        但UndoLastChange只撤消一次(循环)

  

10.可写的recno

   对于Ttable和Tquery的recno是只读的,而TClientDataSet的recno可读可写

            ClientDataSet1.recno:=5;是设第五个记录为当前记录

 

11.数据保存

  对于table使用post可更新数据

  而ClientDataSet1的post只更新内存数据,要更新服务器数据要使用

  ApplyUpdates(MaxErrors: Integer),他有一个参数,是允许发出错误的次数,-1表示无数次,使用simpleobjectbroker时常设为0,实现自动容错和负载平衡

;