ClientDataset的使用

来源:百度文库 编辑:神马文学网 时间:2024/04/28 12:53:01
ClientDataset的使用
嵌套ClientDataset:
1: 当ClientDataset.packetRecords=-1时,不论客户端的ClientDataset.fetchOnDemand和服务端的DatasetProvider.FetchDetailOnDemand如何设置均会导致速度下降到难以忍受。
1.1原因解析:因为服务端datasetProvider指向的主表Dataset会在刚打开时就为每一条纪录整理其Detail
数据(循环发送Sql语句到数据库,并cache到子表的dataset,供以后的Filter使用)。所以服务端的Dataset打开会非常慢。
1.2:解决方案:只有返回一条以编辑或增加一条主表纪录时才使用嵌套ClientDataset。其他浏览时应动态刷新Detail控件。
1.3或者设定ClientDataset.packetRecords为一个比较小的值(100以内)。
1.3 clientdataset的fetchonDemand属性。基本不影响速度(它不影响无服务端封装Detail的行为)。它只设定clientdataset.next时是否自动GetNextPacket.
1.4 DatasetProvider.FetchDetailOnDemand;使用Table作为子表会影响一倍左右的速度(1条主表纪录对应平均4条子表纪录时。使用query作子表时速度基本一样。)
1.5 不论ClientDataset.packetRecords如何设置,服务端的主表qry都从数据库中一次取出全部纪录。
1.6 慎用ClientDataset.IndexName,它会导致读入所有纪录到客户端。
2、结论:对于存在主子表显示或编辑要求的客户端需要使用PacketRecords属性设为较小值。(此类客户端一般不会要求显示太多数据)。可以将服务端的DatasetProvider.FetchDetailOnDemand设置为false影响不大,还可简化客户端代码。对于统计查询的数据不要使用主子表关系。Tips:在子表的Sql中可以返回连接表数据。并设置相关字段为notpfInUpdate,可以简化客户端代码(不用使用计算字段)并能够编辑更新子表数据。
3.所有需要更新的Clientdataset均不能与其他的Clientdataset共享一个DatasetProvider.
4、设置PackageRecordcount<>-1的Clientdataset不能与其他的Clientdataset共享一个DatasetProvider.
5、对于浮点数可能会导致更新ClientDataset出现"Record Changed by Another user"错误,这是由于float截断导致的。将对应的DatasetProvider的Updatemode=whereKeyOnly.将联系的
query字段PKNO的providerFlag设置为pfInkey.
6、怎样更新一个多表连接的结果集?i.设置datasetProvider.onGetTableName事件,设定要更新的表。ii.将相关的query字段(来自其它表的字段)的provideFlag去除pfInputDate属性。
7、将编辑修改用的clientdataset和查询浏览用的clientdataset的provider分开的原因:使客户端的编辑窗口比较独立于其他窗口,降低耦合度,提高编辑浏览详细窗口的通用性。
8、不将编辑修改用的clientdataset和查询浏览用的clientdataset的provider分开的原因:修改后不用重新刷新查询的clientdataset。不用重新定义计算字段、
by chenglh
2003-07-10
2003-11-25 14:09:00
查看评语»»»
2005-8-22 20:21:37    关于主从嵌套TclientDataset补充比较合理的主从表方案:不使用嵌套,但将全部符合条件的主表和从表读入客户端。
然后再客户端处理改变主表位置时的界面显示。这样速度最快。与服务段通讯(round trip)最少。
例子:
2003年的工资单表和明细表。
cds工资单表.sql:=select * from 工资单表 where year =2003
cds工资明细表.sql:=select * from 工资单明细表 where 工资单表ID in (select ID from 工资单表. where year =2003)
客户端的界面自己写代码处理动态显示相应的工资明细情况。
对于修改或增加工资记录。可以写一个比较通用的函数来处理外键的引用一致。(这个函数可以很简单地实现)
2005-9-20 10:15:04    利用TClientDataset作通用的数据库访问如上所述,基本可以不用TClientDataset的嵌套功能。
当不使用嵌套功能时,可以利用TClientDataset的Data和Delta作一个通用的数据库读取和更新的数据模块。(实际上,在大多数情况下,客户端的TClientDataset是可以共用一个TDatasetProvider的)。这样客户端全部可以使用TClientDataset,程序从2层转到3层(或者反过来)时,修改的代码都在十几行内(不论项目有多大)。后面将给出这个实现方法的代码。
Tips:客户端的TClientDaset常常需要设置永久字段,用来更改Field的DisplayLabel,ProviderFlags等,另外,需要加入计算字段时,其他的非计算字段也必须设置为永久字段。 如何给客户端的TClientDataset方便地加入永久字段?  方法是:用普通的方法连接好数据库,设置sql,然后用右键菜单"AddFields",把字段加进去。再进行设置。
Tips:怎样更新来自多表连接的sql取来的数据集cdsJoin? 答:步骤:1、设置cdsJoin的永久字段,2、给不用更新的来自不用更新表中的字段,设置其ProviderFlags.pfInWhere,pfInUpdate均为False   3、用本笔记后面代码中的SaveCdsParital函数保存即可。
下面是一个通用的数据访问代码。(注:本来为该代码定义的一个接口,为了简化,将接口去掉了。)
unit untDmDBDealer;
{

声明:您可以修改和分发本单元的代码。
如果您在项目中使用本代码,请保留该pas文件的文件头部说明。
功能:为untDBRoutine单元提供一个IDBDealer接口的实现。
编制人:程龙华
时间:2005-09-12
注意:本单元为公共单元,不含与任何特定项目有关的业务逻辑。
常用的(标准)使用方法:
1.创建对象aInstance:=TdmDBDealer.create(aOwner);
2.设定对象的连接ADO字符串aInstance.ConnectionStr:=aStr;
3.将untDbRoutine.DBDealer指向该对象。untDbRoutine.DBDealer:=aInstance as IDbDealer;
4.现在才可以正常使用untDbRoutine单元的各个函数。
begin
untDBRoutine.OpenCDS(‘select * from Table where ...‘,ClientDataset1);
...
untDBRoutine.ExecuteSQL(‘update table ...‘);
end;

}
interface
uses
SysUtils,Forms, Classes, ADODB, Provider, DB, Variants, dxDBCtrl, dxDBTL, dxDBTLCl,
dxEdLib, dxDBELib, Controls, dxTL, DBClient, math, Windows,untDBRoutine,untCommon;
type
//数据模块,实现IDBDealer接口.
TdmDBDealer = class(TDataModule{,IDbDealer})
ADOConnection: TADOConnection;
ADOCmd: TADOCommand;
dsOpenData: TADODataSet;
dspOpenData: TDataSetProvider;
cdsTemp: TClientDataSet;
ADOQuery1: TADOQuery;
dsPartialSaveData: TADODataSet;
dspPartialSaveData: TDataSetProvider;
dsSaveData: TADODataSet;
dspSaveData: TDataSetProvider;
procedure dspSaveDataBeforeApplyUpdates(Sender: TObject;
var OwnerData: OleVariant);
procedure DataModuleCreate(Sender: TObject);
procedure dspSaveDataUpdateError(Sender: TObject;
DataSet: TCustomClientDataSet; E: EUpdateError;
UpdateKind: TUpdateKind; var Response: TResolverResponse);
procedure dspParitalSaveUpdateData(Sender: TObject;
DataSet: TCustomClientDataSet);
procedure dspPartialSaveDataGetTableName(Sender: TObject; DataSet: TDataSet;
var TableName: String);
procedure DataModuleDestroy(Sender: TObject);
procedure ADOConnectionWillConnect(Connection: TADOConnection;
var ConnectionString, UserID, Password: WideString;
var ConnectOptions: TConnectOption; var EventStatus: TEventStatus);
procedure dspPartialSaveDataBeforeApplyUpdates(Sender: TObject;
var OwnerData: OleVariant);
private
{ Private declarations }
FLastError:string;
FConectionStr:string;
FIsPartialSave:boolean;
FFields:TStrings;
FTableToSave:string;
function GetConnectionStr: string;
procedure SetConnectionStr(const Value: string);
public
{ Public declarations }
property ConnectionStr:string read GetConnectionStr write SetConnectionStr;
//implenmentation of IDbDealer
function OpenCDS(aSql:string;ACDS:TClientDataset):boolean;
function SaveCdsData(aCDS:TClientDataset):boolean;
function SaveCdsPartial(aCDS:TClientDataset;TableName:string):boolean;
function ExecuteSQL(aSql:string):boolean;
function GetLastError:string;
//end of the IDbDealer implenmentation
end;
function FieldsToText(aFields:TFields):string;
var
dmDBDealer: TdmDBDealer;
implementation
{$R *.dfm}
{ TdmDBDealer }
procedure TdmDBDealer.dspSaveDataBeforeApplyUpdates(Sender: TObject;
var OwnerData: OleVariant);
begin
dsSaveData.CommandText := VartoStr(OwnerData);
end;
procedure TdmDBDealer.DataModuleCreate(Sender: TObject);
begin
ADOConnection.Close;
FFields:=TStringList.Create;
//untDBRoutine.DBDealer:=(self as IDBDealer);
end;
procedure TdmDBDealer.dspSaveDataUpdateError(Sender: TObject;
DataSet: TCustomClientDataSet; E: EUpdateError; UpdateKind: TUpdateKind;
var Response: TResolverResponse);
begin
FLastError:=E.Message;
end;
procedure TdmDBDealer.dspParitalSaveUpdateData(Sender: TObject;
DataSet: TCustomClientDataSet);
var i:integer;
Flags:string;
begin
if not FIsPartialSave then
exit;
for i:=0 to Dataset.Fields.Count-1 do
begin
Flags:=FFields.Values[Dataset.Fields[i].FieldName];
Dataset.Fields[i].ProviderFlags:=[];
if Pos(‘pfInUpdate‘,Flags)>0 then
Dataset.Fields[i].ProviderFlags:=Dataset.Fields[i].ProviderFlags+[pfInUpdate];
if Pos(‘pfInWhere‘,Flags)>0 then
Dataset.Fields[i].ProviderFlags:=Dataset.Fields[i].ProviderFlags+[pfInWhere];
if Pos(‘pfInKey‘,Flags)>0 then
Dataset.Fields[i].ProviderFlags:=Dataset.Fields[i].ProviderFlags+[pfInKey];
if Pos(‘pfHidden‘,Flags)>0 then
Dataset.Fields[i].ProviderFlags:=Dataset.Fields[i].ProviderFlags+[pfHidden];
end;
end;
procedure TdmDBDealer.dspPartialSaveDataGetTableName(Sender: TObject; DataSet: TDataSet;
var TableName: String);
begin
if FIsPartialSave then
TableName:=FTableToSave;
end;
procedure TdmDBDealer.DataModuleDestroy(Sender: TObject);
begin
FFields.free;
if untDbRoutine.DbDealer=(self as IDbDealer)  then
untDBRoutine.SetDBDealer(nil);
end;
function TdmDBDealer.ExecuteSQL(aSql: string): boolean;
begin
try
ADOCmd.CommandText := aSQL;
ADOCmd.CommandType := cmdText;
ADOCmd.Execute;
except
on e: Exception do
begin
FLastError := e.Message;
raise;
end;
end;
result:=(FLastError=‘‘)
end;
function TdmDBDealer.OpenCDS(aSql: string; ACDS: TClientDataset):boolean;
begin
Result:=true;
ACDS.Close;
aCDS.Filtered:=false;
aCDS.Filter:=‘‘;
ACDS.commandText:=aSql;
try
try
dsOpenData.CommandText := aSQL;
dsOpenData.Open;
aCDS.Data:= dspOpenData.Data;
except
on e: Exception do
begin
Result:=false;
raise;
FLastError := e.Message;
end;
end;//inner try
finally
dsOpenData.Active := false;
end;//outer try
end;
function TdmDBDealer.SaveCdsData(aCDS: TClientDataset): boolean;
var
vOwnerData:OleVariant;
iErrCount:integer;
begin
if aCds.ChangeCount>0 then
begin
vOwnerData:=aCDS.commandText;
dspSaveData.ApplyUpdates(aCDS.Delta, 0, iErrCount,vOwnerData );
if FLastError=‘‘ then
aCDS.MergeChangeLog
else
begin
Application.MessageBox(PChar(FLastError),‘保存时出现错误。‘
,mb_ICONINFORMATION+MB_OK);
end;
Result := (FLastError=‘‘)
end
else
result:=true;
end;
function TdmDBDealer.SaveCdsPartial(aCDS: TClientDataset;
TableName: string): boolean;
var
iErrCount:integer;
vOwnerData:OleVariant;
begin
FIsPartialSave:=true;
FFields.Text:=FieldsToText(aCDS.Fields);
FTableToSave:=TableName;
vOwnerData:=aCDS.CommandText;
dspPartialSaveData.ApplyUpdates(aCDS.Delta, 0, iErrCount,vOwnerData);
result := (iErrCount=0);
if iErrCount=0 then
aCDS.MergeChangeLog;
FIsPartialSave:=false;
end;
function TdmDBDealer.GetConnectionStr: string;
begin
result:=ADOConnection.ConnectionString;
end;
procedure TdmDBDealer.SetConnectionStr(const Value: string);
begin
FConectionStr:=value;
end;
procedure TdmDBDealer.ADOConnectionWillConnect(Connection: TADOConnection;
var ConnectionString, UserID, Password: WideString;
var ConnectOptions: TConnectOption; var EventStatus: TEventStatus);
begin
if FConectionStr<>‘‘ then
ConnectionString:=FConectionStr;
end;
procedure TdmDBDealer.dspPartialSaveDataBeforeApplyUpdates(Sender: TObject;
var OwnerData: OleVariant);
begin
dsPartialSaveData.CommandText:=OwnerData;
end;
function TdmDBDealer.GetLastError: string;
begin
Result:=FLastError;
end;
function FieldsToText(aFields:TFields):string;
var i:integer;
sl:TStrings;
pfs:string;
begin
sl:=TStringList.Create;
for i:=0 to aFields.Count-1 do
begin
pfs:=‘‘;
if pfInUpdate in aFields[i].ProviderFlags then
pfs:=pfs+‘pfInUpdate‘;
if pfInWhere in aFields[i].ProviderFlags then
pfs:=pfs+‘,pfInWhere‘;
if pfInKey in aFields[i].ProviderFlags then
pfs:=pfs+‘,pfInKey‘;
if pfHidden in aFields[i].ProviderFlags then
pfs:=pfs+‘,pfHidden‘;
sl.Add(aFields[i].FieldName+‘=‘+pfs);
end;
result:=sl.Text;
sl.free;
end;
end.
问题1:本单元的dfm代码是什么样子?  答:没有贴上对应的dfm代码,如果你对本代码感兴趣,可以自己根据上面的Pas文件恢复一个dfm文件出来。
问题2:本笔记下面列出的代码是基于2层的,怎样做一个3层的通用访问单元?  答:建立一个RemoteDataModule,将本单元中的AdoConnection,TdatasetProvider,TADODataset控件放入其中。编写相应事件代码即可。
-----声明:如果您在项目中使用本代码,请保留该pas文件的文件头部说明。
2005-10-8 14:35:19    大量数据时提高Tclientdataset访问速度的方法。当cdsLarge : Tclientdataset 记录数很多时(3000条以上)
1:locate,lookup,post 访问都会很慢。
2:设置Filter几乎不用时间,但取消Filter会花很长时间。
一个解决方案:
新建一个cdsSmall : Tclientdataset,用cdsSmall.clonecursor(cdsLarge,True),设置cdsSmall的Filter,使cdsSmall记录数较小。在cdsSmall上执行locate,lookup,post 操作。用完不要取消cdsSmall的Filter,直接Free掉cdsSmall。
当在一个循环中有locate,lookup,post时, 这个方法可以将执行速度提高几百倍。
2005-10-13 20:06:52    大量数据时提高Tclientdataset访问速度的方法(补)当使用post函数时,要保证所有共享data的cds,(即clone自同一cds的cds),其过滤后数据集较小。有任意一个没有过滤,或者过滤后仍然记录很多,那么post仍然很慢。