ClientDataset的使用
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
ClientDataset的使用
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中可以返回连接表数据。
并设置相关字段为not pfInUpdate,可以简化客户端代码(不用使用计算字段)并能够编辑更新子表数据。
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,然后用右键菜单"Add Fields",把字段加进去。
再进行设置。
Tips:怎样更新来自多表连接的sql取来的数据集cdsJoin?答:步骤:1、设置cdsJoin的永久字段,2、给不用更新的来自不用更新表中的字段,设置其ProviderFlags.pfInWhere, pfInUpdate均为False 3、用本笔记后面代码中的SaveCdsParital函数保存即可。
下面是一个通用的数据访问代码。
(注:本来为该代码定义的一个接口,为了简化,将接口去掉了。
)
unit untDmDBDealer;
{
<remark>
声明:您可以修改和分发本单元的代码。
如果您在项目中使用本代码,请保留该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;
</remark>
}
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 FieldsT oT ext(aFields:TFields):string;
var
dmDBDealer: TdmDBDealer;
implementation
{$R *.dfm}
{ TdmDBDealer }
procedure
TdmDBDealer.dspSaveDataBeforeApplyUpdates(Sender: TObject;
var OwnerData: OleVariant);
begin
mandText := 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‘,F lags)>0 then
Dataset.Fields[i].ProviderFlags:=Dataset.Fields[i].ProviderFla gs+[pfInUpdate];
if Pos(‘pfInWhere‘,Flags)>0 then
Dataset.Fields[i].ProviderFlags:=Dataset.Fields[i].ProviderFla gs+[pfInWhere];
if Pos(‘pfInKey‘,Flags)>0 then
Dataset.Fields[i].ProviderFlags:=Dataset.Fields[i].ProviderFla gs+[pfInKey];
if Pos(‘pfHidden‘,Flags)>0 then
Dataset.Fields[i].ProviderFlags:=Dataset.Fields[i].ProviderFla gs+[pfHidden];
end;
end;
procedure
TdmDBDealer.dspPartialSaveDataGetT ableName(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
mandText := aSQL;
mandType := 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:=‘‘;
mandText:=aSql;
try
try
mandText := 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:=mandText;
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:=mandText;
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
mandText:=OwnerData;
end;
function TdmDBDealer.GetLastError: string;
begin
Result:=FLastError;
end;
function FieldsT oT ext(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,T datasetProvider,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仍然很慢。