ClientDataSet 心得

  1.   与TTable、TQuery一样,TClientDataSet也是从TDataSet继承下来的,它通常用于多层体系结构的客户端。很多数据库应用程序都用了BDE,BDE往往给发布带来很大的不便,因而TClientDataSet最大的特点是它不依赖于BDE(Borland Database Engine),但它需要一个动态链接库的支持,这个动态链接库叫DBCLIENT.DLL。在客户端,也不需要用TDatabase构件,因为客户端并不直接连接数据库。由于TClientDataSet是从TDataSet继承下来的,所以,它支持诸如编辑、搜索、浏览、纠错、过滤等功能。由于 TClientDataSet在内存中建立了数据的本地副本,上述操作的执行速度很快。也正是由于TClientDataSet并不直接连接数据库,因此,客户程序必须提供获取数据的机制。
在Delphi 4中,TClientDataSet有三种途径获取数据:
1、从文件中存取数据。
2、从本地的另一个数据集中获取数据。
3、通过IProvider接口从远程数据库服务器获取数据。
在一个客户程序中,可以同时运用上述三种机制获取数据。

和其他数据集构件一样,可以用标准的数据控件显示由TClientDataSet引入的数据集,当然,这需要借助于TDataSource构件。由于 TClientDataSet是从TDataSet继承下来的,所以,凡是其他数据集构件支持的功能,TClientDataSet构件也大致具备。不同的是,TClientDataSet能够在内存中建立数据的副本,因此,TClientDataSet比其他数据集构件增加了一些特殊的功能。
在运行期,可以调用诸如First、GotoKey、Last、Next和Prior等函数来浏览数据。TClientDataSet也支持书签 (BookMark)功能,可以用书签来标记某条记录,以后就可以方便地找到这条记录。对于TTable、TQuery等数据集构件来说,只能读 RecNo属性来判断当前记录的序号。对于TClientDataSet构件来说,还可以写RecNo属性,使某一序号的记录成为当前记录。

1、从文件中存取数据要从文件中读取数据,可以调用LoadFromFile函数。LoadFromFile函数需要传递一个参数,用于指定文件名。文件名应包含完整的路径。如果客户程序总是从一个固定的文件中读取数据,可以设置FileName属性指定一个文件名,以后,当TClientDataSet 引入的数据集打开时,就自动从这个文件中读取数据,不需要调用LoadFromFile。要从流中读取数据,可以调用LoadFromStream。 LoadFromStream需要传递一个参数,用于指定一个流对象。注意:LoadFromFile(LoadFromStream)只能从先前用 SaveToFile(SaveToStream)保存的文件中读取数据。要把数据保存到文件中,可以调用SaveToFile函数。 SaveToFile需要传递一个参数,用于指定文件名。如果指定的文件已存在,文件中的数据将被覆盖。如果客户程序总是把数据保存到一个固定的文件中,可以设置FileName属性指定一个文件名,当TClientDataSet引入的数据集关闭时,就自动把数据保存到这个文件中,不需要调用 SaveToFile。要把数据保存到流中,可以调用SaveToStream。SaveToStream需要传递一个参数,指定一个流对象。注意:当把数据保存到文件或流中时,日志中记载的修改仍然保留。这样,当下次调用LoadFromFile或LoadFromStream读取数据时,仍然可以恢复原来的数据。

ClientDataSet强大的数据复制技术:
   通过ClientDataSet.Data属性可以访问客户程序从应用服务器检索到的数据。程序示例如下: 

   Procedure TForm1.Button1Click(Sender: TObject); 

   Begin 

     ClientDataSet1.Data := 

     ClientDataSet1.Provider.DataRequest(FilterEdit.Text);//在Delphi4版本之上有所改变 

   End; 

   也可以直接赋值:    

  ClientDataSet1.Data:=ClientDataSet2.Data;//(相当于把ClientDataSet2的数据拷贝给ClientDataSet1,是不是很方便)

   从其他数据集获取数据(除ClientDataSet):

   DataSetProvider1.DataSet:=DataSet;//DataSet代表一个数据集 

   ClientDataSet1.Data := DataSetProvider1.Data; 



3. 排序
ClientDataSet排序
1、简单排序

ClientDataSet1.IndexFieldNames:='排序字段' 

2、复杂排序(建立索引)
下面这个过程仅供参考(因为用到三方控件DBGridEh):

procedure TDM1.DsSort(SortColumn: TColumnEh); 

var 

OldIndex:string; 

begin 

if (SortColumn.Grid.DataSource=nil) or (SortColumn.Grid.DataSource.DataSet=nil) or (not SortColumn.Grid.DataSource.DataSet.Active) then Exit;

OldIndex:=TClientDataSet(SortColumn.Field.DataSet).IndexName; 

if OldIndex<>'' then 

begin 

   TClientDataSet(SortColumn.Field.DataSet).IndexName:=''; 

   TClientDataSet(SortColumn.Field.DataSet).DeleteIndex(OldIndex); 

end; 

case SortColumn.Title.SortMarker of 

   smNoneEh, 

   smUpEh   :TClientDataSet(SortColumn.Field.DataSet).AddIndex('px',SortColumn.Field.FieldName,[ixDescending]);

   smDownEh:TClientDataSet(SortColumn.Field.DataSet).AddIndex('px',SortColumn.Field.FieldName,[ixPrimary]);

end; 

TClientDataSet(SortColumn.Field.DataSet).IndexName:='px'; 

end; 


把上面的过程稍做修改,可用于标准DBGridvar

ASC:Boolean=True;//是否升序排列 

procedure TDM1.DsSort(SortColumn: TColumn); 

var 

OldIndex:string; 

begin 

if (SortColumn.Grid.DataSource=nil) or (SortColumn.Grid.DataSource.DataSet=nil) or (not SortColumn.Grid.DataSource.DataSet.Active) then Exit;

OldIndex:=TClientDataSet(SortColumn.Field.DataSet).IndexName; 

if OldIndex<>'' then 

begin 

TClientDataSet(SortColumn.Field.DataSet).IndexName:=''; 

TClientDataSet(SortColumn.Field.DataSet).DeleteIndex(OldIndex); 

end; 

case ASC of 

Ture :TClientDataSet(SortColumn.Field.DataSet).AddIndex('px',SortColumn.Field.FieldName,[ixDescending]);//已经是升序就按降序排列

else//否则按升序排列 

TClientDataSet(SortColumn.Field.DataSet).AddIndex('px',SortColumn.Field.FieldName,[ixPrimary]);

end;{end case} 

TClientDataSet(SortColumn.Field.DataSet).IndexName:='px'; 

ASC:=not ASC; 

end; 


4. 提交更新过程:
首先,客户程序要调用ApplyUpdates函数向应用服务器提出申请,ApplyUpdates函数将通过IProvider接口把Delta(数据变动情况)属性传递给应用服务器。应用服务器收到客户程序的申请后,再向远程数据库服务器提出申请,并且把被远程数据库服务器认为出错的记录暂时缓存起来。应用服务器上的TDataSetProvider或TProvider构件把出错的记录返回给客户程序,其中包括错误信息和错误代码。客户程序收到这些出错的记录后,可以进行核对和修改,然后继续更新。注意:如果应用服务器端使用MTS类型的远程数据模块,就无法提供IProvider接口,这种情况下,必须通过远程数据模块的接口直接申请更新数据。
if ClientDataSet1.ChangeCount>0 then//有未决的修改
   ClientDataSet1.ApplyUpdates(MaxErrors);//将修改提交到服务器
参数MaxErrors用于指定一个最大错误数,如果出错的记录数超过了这个参数的值,此次更新就停止。如果MaxErrors参数设为0,只要应用服务器发现有一个错误的记录,更新操作就停止。如果MaxErrors参数设为-1,当应用服务器发现有错误的记录,就尝试更新下一个记录,等所有的记录都尝试过以后才返回。ApplyUpdates函数将返回实际遇到的错误数,同时,应用服务器将返回那些有错误的记录。
当应用服务器收到客户的提交请求后,触发OnUpdateData,这时就可以对客户提交的数据进行检查和编辑:

Procedure TDataModule1.Provider1UpdateData(Sender:TObject;DataSet: TClientDataSet);

Begin 

With DataSet Do 

Begin 

    First; 

    While not Eof Do 

    Begin 

      If UpdateStatus = usInserted then 

      Begin 

        Edit; 

        FieldByName('DateCreated').AsDateTime := Date; 

        Post; 

      End; 

      Next; 

    End; 

End; 

End; 


然后将编辑后的数据提交到数据库服务器。
ClientDataSet1.CancelUpdates;//恢复所有修改过但未提交(包括提交未成功的)的记录
ClientDataSet1.UndoLastChange;//恢复前一次的修改,相当于Undo功能
注意使用这种提交方式(ApplyUpdates)在查询时尽可能避免使用数据处理(关联、分组、求和等等等),否则不能提交(除非自己写一些特殊处理程序)


在三层结构中,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,实现自动容错和负载平衡 


ClientDataSet使用心得和技巧

影响ClientDataSet处理速度的一个因素
TClientDataSet是Delphi开发数据库时一个非常好的控件。有很强大的功能。
我常常用ClientDataSet做MemoryDataSet来使用。还可以将ClientDataSet的数据保存为XML,这样就可以做简单的本地数据库使用。还有很多功能就不多说了。在使用ClientDataSet的过程中关于怎样提高处理速度这个问题,我就我个人的一点点体会和大家分享一下。

通常情况下我们一般都是用
...ClientDataSet-->DataSource-->DBComponent
这样的结构,处理数据的时候就直接操作ClientDataSet。但是大多DBComponet都会立即响应ClientDataSet的变化。如果你是向ClientDataSet中插入很多数据时候,DBComponent就要响应几次,而且响应过程根据不同的控件,速度,过程数量都不一样。这样就影响了程序的执行效率 。所以在对ClientDataSet处理中,我是用ClientDataSet.DisableControls和ClientDataSet.EnableControls方法:打开和关闭DBComponent与ClientDataSet的数据显示关系。
例如:
ClientDataSet..DisableControls;

...

for I := 0 to 10000 do

begin

ClientDataSet.Append;

...

ClientDataSet.Post;

end;

...

ClientDataSet.EnableControls

...


这样做以后你会发现处理速度比以前没有使用方法的时候有成倍的提高。

ClientDataSet的数据查找。
我所介绍的心得和技巧都是用ClientDataSet来做范例,也可以应用于其他的一些DataSet。废话就不多说了。我们还是先看代码,让后再总结。
1.Scanning 扫描数据查找
这是最简单最直接也是最慢的一种方法,遍历所有数据:
procedure TForm1.ScanBtnClick(Sender: TObject);

var

  Found: Boolean;

begin

  Found := False;

  ClientDataSet1.DisableControls;

  Start;

  try

    ClientDataSet1.First;

    while not ClientDataSet1.Eof do

    begin

      if ClientDataSet1.Fields[FieldListComboBox.ItemIndex].value = SearchText then

      begin

        Found := True;

        Break;

      end;

      ClientDataSet1.Next;

    end;

    Done;

  finally

    ClientDataSet1.EnableControls;

  end;

  if Found then

    ShowMessage(SearchText + ' found at record ' + IntToStr(ClientDataSet1.RecNo))

  else

    ShowMessage(ScanForEdit.Text + ' not found');

end;

2.Finding 寻找数据
最老,但是最快 的查找方式。
使用FindKey/FindNearest来查找一条或多条符合条件的数据,当然待查找的Field必须是一个IndexField 。可以看出,这种基于Index的查找速度是非常快的。
procedure TForm1.FindKeyBtnClick(Sender:TObject);

begin

  Start;

  if ClientDataSet1.FindKey([SearchText]) then

  begin

    Done;

    StatusBar1.Panels[3].Text := SearchText + ' found at record ' + IntToStr(ClientDataSet1.RecNo);

  end

  else

  begin

    Done;

    StatusBar1.Panels[3].Text := SearchText + ' not found';

  end;

end;



procedure TForm1.FindNearestBtnClick(Sender: TObject);

begin

  Start;

  ClientDataSet1.FindNearest([SearchText ]);

  Done;

  StatusBar1.Panels[3].Text := 'The nearest match to ' + SearchText + ' found at record ' + IntToStr(ClientDataSet1.RecNo);

end

3.Going 定位
GotoKey/GotoNearest 与FindKey/FindNearest基本上没有什么区别。它也是基于Index的查找。唯一的区别就是在于你是怎么定义你的查找了。代码上也有区别:
ClientDataSet1.SetKey;

ClientDataSet1.FieldByName(IndexFieldName).value := SearchText;

ClientDataSet1.GotoKey;
 
就相当于
ClientDataSet1.FindKey([SearchText]);

要用好这两种基于Index的查找,还需要了解ClientDataSet和Index机制。这里就不详细说明Index机制。一个基本的原则,要有Index,才能查找。

4.Locating 查找数据
2,3两种查找方式都是基于Index的,但是在实际应用中,可能会查找IndexField以外的Field。那我们就可以使用Locate。但是查找速度是没有2,3两种快的。比如:如果你查找一条纪录9000/10000,Locate需要500ms,Scanning需要>2s,FindKey只要10ms(但是当你打开ClientData的时候,建立Index需要1s)。
procedure TForm1.LocateBtnClick(Sender: TObject);

begin

  Start;

  if ClientDataSet1. Locate('Field1,Field2..',VarArrayOf['value1,value2..'], []) then

  begin

    Done;

    StatusBar1.Panels[3].Text := 'Match located at record ' +

    IntToStr(ClientDataSet1.RecNo);

  end

  else

  begin

    Done;

    StatusBar1.Panels[3].Text := 'No match located';

  end;

end;

小结:
ClientDataSet提供了好多种查找数据的方法。但是各自有其优缺点。
上面的例子中有Start;和Done,如果你有兴趣,可以加入计时点进行速度测试。
Scanning最简单,但是最慢,因为比较慢,还得使用ClientDataSet.DisableControls和ClientDataSet.EnableControls方法(我在前面一片文章讲过)。
Findkey/FindNearest(GotoKey/GotoNearest)代码多,但是非常快。必须使用Index,不同的是Find需要的Index是必须建立好的,而Goto可以在第一次使用时建立Index。
Locate使用最方便,不需要Index,但是速度没有Find快

你可能感兴趣的:(client)