概述
用 Delphi 做三层的数据库应用,如果使用 MIDAS 框架,比较典型的一种用法是使用 WebService 框架。客户端使用 ClientDataSet 作为一个内存表,使用 DBGridEh 把数据呈现给用户。
服务器端则采用任何的数据库控件,比如现在的 Delphi 版本推荐的 FireDAC 那一套。或者比较传统的 ADO 那一套(如果你使用 MS SQL-SERVER)。
如果数据库服务器里面某个表的记录条数非常多,比如几万条,使用 select * from MyTable 把几万条数据一次拉到客户端,没必要,也浪费网络传输数据的时间,用户会觉得程序卡住了。
因此,需要分页。
实现方法
场景一
如果客户端和服务器端的连接是 Delphi 提供的 Socket 连接,也就是客户端和服务器端保持了一个 TCP 的长连接,那么,服务器端会为每个连接上来的客户端维护状态。在这种情况下,客户端分页就很简单:
1. 服务器端从数据库取数据:select * from MyTable;
2. 客户端的 ClientDataSet 设置它的属性:PacketRecords 的值为 100,表示你想要的一页的记录数,也就是每次只拉 100 条记录到客户端。这个属性的默认值是 -1,表示 ClientDataSet 在 Open 的时候会一次把服务器端的全部记录都拉到客户端;
3. DBGrid 或者 DBGridEh 通过 DataSource 连接到 ClientDataSet。
4. 执行 ClientDataSet1.Open 你会看到 DBGrid 里面,有 100 条记录;
5. 用鼠标或者键盘,让 DBGrid 的游标往下滚,当游标滚到最后一条记录的时候,ClientDataSet1 会自动向服务器端再拉100条记录到客户端,此时你会看到 DBGrid 里面,有 200 条记录。
到此,分页抓取数据就实现了。
注意的事项:
如果是使用 DBGridEh ,需要关掉它的 SumList,也就是 DBGridEh1.SumList.Active := False;
假设 SumList.Active 这个属性是 True,那么 DBGridEh 内部的代码会持续请求下一页,那分页获取数据的设置就没有意义了。
场景二
如果网络架构采用 WebService 的模式,则服务器端和客户端之间没有保持连接。好处是服务器端可以同时服务更多的客户端,节约硬件成本;坏处是服务器端没有为客户端保持游标状态,因此要实现场景一描述的功能,需要多做一点事情。
1. 同样的,设置 ClientDataSet1.PacketSize := 100; (当然你也可以设置为 200,或者 1000);
2. 服务器端:假设我们的表有一个整数的编号字段(比如自增字段)名字叫 SNO,SQL 语句写成: select top 100 * from MyTable where SNO>:SNO order by SNO(SQL SERVER)或者是:select First 100 * from MyTable where SNO>:SNO order by SNO;这个SQL的意思就是按照编号排序取前面 100 条。
3. ClientDataSet1.BeforeGetRecords 事件方法里面,把 SQL 语句需要的参数传过去:
procedure TForm1.ClientDataSet1BeforeGetRecords(Sender: TObject;
var OwnerData: OleVariant);
var
i: Int64;
begin
if not ClientDataSet1.Active then
begin
i := -1;
end
else
begin
i := ClientDataSet1.FieldByName('SNO').AsLargeInt;
end;
OwnerData := i;
end;
这里给 OwnerData 赋值,会在客户端执行 MIDAS 的数据请求时,传递到服务器端的这个 ClientDataSet1 对应的 DataSerProvider1 触发 DataSetProvider1 的 BeforeGetRecords 事件。
4. 因此,在服务器端的对应事件里面写代码把客户端传递过来的参数,给 SQL 语句使用。这里我使用的是 FireDAC 的 FdQuery 控件来执行 SQL 语句:
procedure TForm1.ClientDataSet1AfterGetRecords(Sender: TObject;
var OwnerData: OleVariant);
begin
FdQuery1.Params[0].Value := OwnerData;
end;
5. 上面客户端的代码,和服务器端的代码,共同作用,当 DBGrid 里面的数据滚动到结尾,会触发 ClientDataSet1 自动去向服务器请求下一包数据。此时会触发 ClientDataSet1.BeforeGetRecords 事件方法,在这个方法里面,把最后一条记录的编号传递给服务器端;然后在服务器端收到参数后会触发 DataSetProvider1.BeforeGetRecords 事件方法,在这里把这个编号值赋予 FdQuery1,我们就能够得到正确的下一个分页的数据了。
注意之一
ClientDataSet1.PacketRecord := 100(或者任意一个正整数)则它的游标滚动到最底部时,它会自动向服务器端请求下一包数据(GetNextPacket);假设这个值是默认的 -1 则此时它不会自动请求下一包。
注意之二
假设 ClientDataSet1.PacketRecord := -1; 也就是它的默认值,当游标滚动到最后,它不会自动请求下一包。如果一定要这样做,还是有办法的。请参考本博客之前的一篇文章。
注意之三
同样,这里不能让 DBGridEh 的 SumList.Active 属性为 True,否则它会自动连续请求下一页,也就失去分页获取数据的意义了。
但是,如果我想知道究竟抓了多少条数据到客户端,本来是可以利用 DBGridEh 的 Footer 的一个计算或者汇总功能的,把某个字段的页脚的属性 Footer.ValueType 设置为 fvtCount 就能知道当前客户端有多少条记录,但因为不能打开 SumList 这个功能,这里的 Footer 就不会显示当前数据的记录条数,只能显示数字 0;
那我想要看到当前有多少条记录,怎么办?找到 DBGridEh 在属性面板里面的 IndicatorOptions 里面,勾选上 gioShowRecNoEh,它就会给每条记录显示一个连续的记录编号(这个编号是DBGridEh 作为客户端控件,本地为当前的内存里面的记录创建的连续编号),也会在页脚显示当前的总记录条数。
1018

被折叠的 条评论
为什么被折叠?



