unit UDataModule3;
{------------------------------------------------------------------------------
ClientDataSet 分段获取数据。有状态模式下,服务器端 select 出来100条,如果设置 ClientDataSet.PacketRecords := 10,
则 ClientDataSet 一次获取10条,ClientDataSet.GetNextPacket 获取后继 10 条。服务器端缓存住 1000 条,并帮客户端维护游标。
在无状态模式下,服务器端不帮客户端维护游标。而且,如果记录太多,不应该让服务器端一次取出 1000 条并缓存在服务器内存里面。
所以,这种情况下,服务器端应该是 Select first 10 * from MyTable
然后,客户端每次需要读取后面的数据时,就执行 ClientDataSet.GetNextPacket; 方法来向服务器请求下一段数据。
但是,这里的问题是:第一次读取数据,服务器里面只有10条,所有10条都已经读回客户端,
则 ClientDataSet.GetNextPacket 时不会有向服务器读取数据的动作。查其代码,里面会判断一下 ProviderEOF 属性。
所以,这里如果服务器端没有缓冲比10条更多的数据,则必须要重新设置 ProviderEOF 属性为 False 才能让它执行 GetNextPacket。
但是,ProviderEOF 属性是 Protected 的,在程序里无法访问。
So, 这里必须 Hack 一下 TClientDataSet。做法是这样的:
Type
TMyClientDataSet = class(TClientDataSet)
public
property ProviderEOF;
end;
通过继承,把 ProviderEOF 属性公开。
然后,在 ClientDataSet2 需要设置 ProviderEOF 属性的地方,就可以这样:
TMyClientDataSet(ClientDataSet2).ProviderEOF := False;
然后,再执行 ClientDataSet2.GetNextPacket; 它就会去向服务器请求下一段数据了。
然后,服务器怎么知道从哪里开始读取下一段数据?
在 ClientDataSet2.BeforeGetRecords 事件里面,将 OwnerData 设置为当前记录排序字段的最大值;
在服务器端的 DataSetProvider2.BeforeGetRecords 里面,为服务器端的 TQuery 的 select 参数赋值为 OwnerData。
这个程序的例子,排序字段就是主键字段,是一个整数编号字段。
很多时候,排序字段可以是时间字段。
以下代码测试通过。
pcplayer 2016-6-23
------------------------------------------------------------------------------}
interface
uses
System.SysUtils, System.Classes, FireDAC.UI.Intf, FireDAC.VCLUI.Wait,
FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Error, FireDAC.Phys.Intf,
FireDAC.Stan.Def, FireDAC.Stan.Pool, FireDAC.Stan.Async, FireDAC.Phys,
FireDAC.Phys.FB, FireDAC.Phys.FBDef, FireDAC.Stan.Param, FireDAC.DatS,
FireDAC.DApt.Intf, FireDAC.DApt, FireDAC.Comp.Client, FireDAC.Comp.DataSet,
Data.DB, Datasnap.DBClient, Datasnap.Provider, FireDAC.Comp.UI;
type
TDataModule3 = class(TDataModule)
FDGUIxWaitCursor1: TFDGUIxWaitCursor;
FDConnection1: TFDConnection;
FDManager1: TFDManager;
FDTransaction1: TFDTransaction;
DataSetProvider1: TDataSetProvider;
ClientDataSet1: TClientDataSet;
ClientDataSet1XUHAO: TIntegerField;
ClientDataSet1ID: TWideStringField;
ClientDataSet1FULLNAME: TWideStringField;
ClientDataSet1EMAIL: TWideStringField;
DataSource1: TDataSource;
FDQuery1: TFDQuery;
FDStoredProc1: TFDStoredProc;
FDQuery2: TFDQuery;
DataSetProvider2: TDataSetProvider;
ClientDataSet2: TClientDataSet;
DataSource2: TDataSource;
CldTemp: TClientDataSet;
procedure DataSetProvider2BeforeGetRecords(Sender: TObject;
var OwnerData: OleVariant);
procedure ClientDataSet2BeforeGetRecords(Sender: TObject;
var OwnerData: OleVariant);
private
{ Private declarations }
public
{ Public declarations }
procedure OpenClientDataSet2;
end;
//Hack 一下 TClientDataSet 以便这里能访问到其 ProviderEOF 属性。
TMyClientDataSet = class(TClientDataSet)
public
property ProviderEOF;
end;
var
DataModule3: TDataModule3;
implementation
{%CLASSGROUP 'Vcl.Controls.TControl'}
{$R *.dfm}
{ TDataModule3 }
{-------------------------------------------------------------------------------
这里的 FDQuery2 的 SQL 是:select first 10 * from MyTable
这个 MyTable 里面的第一个主键字段是一个整数字段,叫做 XuHao
这里的 ClientDataSet2.PacketRecords 是默认的 -1。因为这是无状态模式。
-------------------------------------------------------------------------------}
procedure TDataModule3.ClientDataSet2BeforeGetRecords(Sender: TObject;
var OwnerData: OleVariant);
begin
OwnerData := ClientDataSet2.Params[0].Value; //将当前排序最大字段值传递给服务器。
end;
procedure TDataModule3.DataSetProvider2BeforeGetRecords(Sender: TObject;
var OwnerData: OleVariant);
begin
FDQuery2.Params[0].Value := OwnerData; //给 服务器端的 select 参数赋值。
end;
procedure TDataModule3.OpenClientDataSet2;
begin
with ClientDataSet2 do
begin
if not Active then
begin
Params[0].Value := 0;
Open;
Exit;
end;
CldTemp.CloneCursor(ClientDataSet2, True);
CldTemp.Last;
Params[0].Value := CldTemp.FieldByName('XuHao').AsInteger;
TMyClientDataSet(ClientDataSet2).ProviderEOF := False; //设置 ProviderEOF 为 False,后面的 GetNexPacket 才会被执行。
ClientDataSet2.GetNextPacket;
end;
end;
end.
----------------
又及:除了分段读取数据,这个方法还可以用于 ClientDataSet 已经根据某个服务器端的参数获取了一组数据,然后更换参数获取新数据,但原有数据不丢的情况。
通常做法是 Close; Params[0].Value := 'New param'; Open;但这样一来,原有数据在 ClientDataSet 中丢失了。现在可以不用做 Close 而是 GetNextPacket 的方式来获取新的参数的数据。测试通过。