1. 与TTable、TQuery同样,TClientDataSet也是从TDataSet继承下来的,它一般用于多层体系结构的客户端。不少数据库应用程序都用了BDE,BDE每每给发布带来很大的不便,于是TClientDataSet最大的特色是它不依赖于BDE(Borland Database Engine),但它须要一个动态连接库的支持,这个动态连接库叫DBCLIENT.DLL。在客户端,也不须要用TDatabase构件,由于客户端并不直接链接数据库。因为TClientDataSet是从TDataSet继承下来的,因此,它支持诸如编辑、搜索、浏览、纠错、过滤等功能。因为 TClientDataSet在内存中创建了数据的本地副本,上述操做的执行速度很快。也正是因为TClientDataSet并不直接链接数据库,所以,客户程序必须提供获取数据的机制。
在Delphi 4中,TClientDataSet有三种途径获取数据:
一、从文件中存取数据。
二、从本地的另外一个数据集中获取数据。
三、经过IProvider接口从远程数据库服务器获取数据。
在一个客户程序中,能够同时运用上述三种机制获取数据。
和其余数据集构件同样,能够用标准的数据控件显示由TClientDataSet引入的数据集,固然,这须要借助于TDataSource构件。因为 TClientDataSet是从TDataSet继承下来的,因此,凡是其余数据集构件支持的功能,TClientDataSet构件也大体具有。不一样的是,TClientDataSet可以在内存中创建数据的副本,所以,TClientDataSet比其余数据集构件增长了一些特殊的功能。
在运行期,能够调用诸如First、GotoKey、Last、Next和Prior等函数来浏览数据。TClientDataSet也支持书签 (BookMark)功能,能够用书签来标记某条记录,之后就能够方便地找到这条记录。对于TTable、TQuery等数据集构件来讲,只能读 RecNo属性来判断当前记录的序号。对于TClientDataSet构件来讲,还能够写RecNo属性,使某一序号的记录成为当前记录。
一、从文件中存取数据要从文件中读取数据,能够调用LoadFromFile函数。LoadFromFile函数须要传递一个参数,用于指定文件名。文件名应包含完整的路径。若是客户程序老是从一个固定的文件中读取数据,能够设置FileName属性指定一个文件名,之后,当TClientDataSet 引入的数据集打开时,就自动从这个文件中读取数据,不须要调用LoadFromFile。要从流中读取数据,能够调用LoadFromStream。 LoadFromStream须要传递一个参数,用于指定一个流对象。注意:LoadFromFile(LoadFromStream)只能从先前用 SaveToFile(SaveToStream)保存的文件中读取数据。要把数据保存到文件中,能够调用SaveToFile函数。 SaveToFile须要传递一个参数,用于指定文件名。若是指定的文件已存在,文件中的数据将被覆盖。若是客户程序老是把数据保存到一个固定的文件中,能够设置FileName属性指定一个文件名,当TClientDataSet引入的数据集关闭时,就自动把数据保存到这个文件中,不须要调用 SaveToFile。要把数据保存到流中,能够调用SaveToStream。SaveToStream须要传递一个参数,指定一个流对象。注意:当把数据保存到文件或流中时,日志中记载的修改仍然保留。这样,当下次调用LoadFromFile或LoadFromStream读取数据时,仍然能够恢复原来的数据。
ClientDataSet强大的数据复制技术:
经过ClientDataSet.Data属性能够访问客户程序从应用服务器检索到的数据。程序示例以下: html
也能够直接赋值: sql
ClientDataSet1.Data:=ClientDataSet2.Data;//(至关于把ClientDataSet2的数据拷贝给ClientDataSet1,是否是很方便) 数据库
从其余数据集获取数据(除ClientDataSet):express
3. 排序
ClientDataSet排序
一、简单排序缓存
二、复杂排序(创建索引)
下面这个过程仅供参考(由于用到三方控件DBGridEh): 服务器
把上面的过程稍作修改,可用于标准DBGridvaride
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)在查询时尽量避免使用数据处理(关联、分组、求和等等等),不然不能提交(除非本身写一些特殊处理程序)post
procedure TForm1.DBGrid1TitleClick(Column: TColumn); begin if (not column.Field is Tblobfield) then//Tblobfield不能索引,二进制 ClientDataSet1.IndexFieldNames:=column.Field.FieldName; end;</span>
ClientDataSet2.Data:=ClientDataSet1.Data; 测试
ClientDataSet2.Open;
ClientDataSet2.CloneCursor(ClientDataSet1,true);
ClientDataSet2.Open;
小结:
ClientDataSet提供了好多种查找数据的方法。可是各自有其优缺点。
上面的例子中有Start;和Done,若是你有兴趣,能够加入计时点进行速度测试。
Scanning最简单,可是最慢,由于比较慢,还得使用ClientDataSet.DisableControls和ClientDataSet.EnableControls方法(我在前面一片文章讲过)。
Findkey/FindNearest(GotoKey/GotoNearest)代码多,可是很是快。必须使用Index,不一样的是Find须要的Index是必须创建好的,而Goto能够在第一次使用时创建Index。
Locate使用最方便,不须要Index,可是速度没有Find快
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; 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 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快。 var sFields : String; vResult : Variant; iCount : Integer; begin vResult := ds.Lookup('fieldnameA, fieldnameB' , VarArrayCreate([ValueA, ValueB], varVariant), 'fieldname1, fieldname2'); if (VarIsArray(vResult)) then begin sFields := ''; for iCount := VarArrayLowBound(vResult, 1) to VarArrayHighBound(vResult, 1) do begin sFields := sFields + ';' + vResult[iCount]; end; end else edtReturn.Text := vResult; end;