如何在内部处理过滤后的 tDataSet 记录而不显示在 tDBGrid 结果上

How to internally process filtered tDataSet records not to be shown on tDBGrid the result

在下面的 tFDMemTable 中,我尝试对 ID 字段以字母 A.A1、A2 开头的记录的值求和,结果应为 4。

type
  TForm1 = class(TForm)
    FDMemTable1: TFDMemTable;
    DBGrid1: TDBGrid;
    DataSource1: TDataSource;
    Button1: TButton;
    Button2: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  end;

procedure TForm1.FormCreate(Sender: TObject);
var
  _FieldDef: TFieldDef;
begin
  _FieldDef := FDMemTable1.FieldDefs.AddFieldDef;

  _FieldDef.Name := 'ID';
  _FieldDef.DataType := ftString;
  _FieldDef.Size := 5;

  _FieldDef := FDMemTable1.FieldDefs.AddFieldDef;

  _FieldDef.Name :='value';
  _FieldDef.DataType := ftInteger;

  FDMemTable1.CreateDataSet;

  FDMemTable1.Append;

  FDMemTable1.FieldValues['ID'] := 'A1';
  FDMemTable1.FieldValues['value'] := 1;

  FDMemTable1.Append;

  FDMemTable1.FieldValues['ID'] := 'B1';
  FDMemTable1.FieldValues['value'] := 2;

  FDMemTable1.Append;

  FDMemTable1.FieldValues['ID'] := 'A2';
  FDMemTable1.FieldValues['value'] := 3;

  FDMemTable1.Append;

  FDMemTable1.FieldValues['ID'] := 'B2';
  FDMemTable1.FieldValues['value'] := 4;
end;

我写了下面的代码,但它改变了过滤后的 tDBGrid。我想要的只是一个内部过程,tDBGrid 应该保持不变。

procedure TForm1.Button1Click(Sender: TObject);
var
  _ValueSum: Integer;
  i: Integer;
begin
  FDMemTable1.Filter := 'ID like ' + QuotedStr('A%');

  FDMemTable1.Filtered := True;

  _ValueSum := 0;

  FDMemTable1.FindFirst;

  for i := 0 to FDMemTable1.RecordCount - 1 do
  begin
    _ValueSum := _ValueSum + FDMemTable1.FieldValues['value'];

    FDMemTable1.FindNext;
  end;

  Button1.Caption := IntToStr(_ValueSum);
end;

我知道 tDataSet.Locate 不允许 NEXT SEARCH 我尝试了这样一种原始方式。它工作正常,但似乎有点愚蠢。

procedure TForm1.Button2Click(Sender: TObject);
var
  _ValueSum: Integer;
  i: Integer;
begin
  _ValueSum := 0;

  FDMemTable1.First;

  for i := 0 to FDMemTable1.RecordCount do
  begin
    if Copy(FDMemTable1.FieldValues['ID'], 1, 1) = 'A' then
    begin
      _ValueSum := _ValueSum + FDMemTable1.FieldValues['value'];
    end;

    FDMemTable1.FindNext;
  end;

  Button2.Caption := IntToStr(_ValueSum);
end;

当我断开 tFDMemTable 和 tDBGrid 或在过滤前设置非活动状态以保持最后一个网格状态时,网格变为空白。最后一个代码是最好的解决方案还是有更好的方法在过滤工作时显示未过滤的结果?

有几件事,如果不是 "wrong",与您的代码不太相符。

  1. 您应该使用 Next,而不是 FindNext 来移动到数据集中的下一行。 Next 移动到数据集中的下一行,而 FindNext 移动到与您已经设置的搜索条件匹配的下一行,例如使用 DataSet.SetKey; ... - 阅读联机帮助了解 FindKey 用法。

  2. 您不应该尝试使用 For 循环遍历数据集;使用 While not FDMemData.Eof do 循环。 Eof 代表 'End of file',一旦数据集位于最后一行,return 为真。

  3. 您应该在循环之前调用 FDMemTable1.DisableControls 并在循环之后调用 FDMemTable1.EnableControls。这可以防止 db-aware 像您的 DBGrid 这样的控件在循环内更新,否则会在网格更新时减慢循环速度。

  4. 除非你有充分的理由不这样做,否则请始终以与设置它相同的方法清除数据集过滤器,否则如果你忘记过滤器处于活动状态,你可能会得到一些非常混乱的错误。

  5. 尽量避免在非绝对需要时使用 RecordCount。根据您使用的 RDMS,它可能会在服务器和网络上造成大量本可以避免的处理开销(因为对于某些服务器类型,它会导致将整个数据集检索到客户端)。

将您的第一个循环更改为

procedure TForm1.Button1Click(Sender: TObject);
var
  _ValueSum : Integer;
begin
  _ValueSum := 0;

  FDMemTable1.Filter := 'ID like ' + QuotedStr('A%');

  try
    FDMemTable1.DisableControls;
    FDMemTable1.First;
    while not FDMemTable1.Eof do begin
      _ValueSum:= _ValueSum + FDMemTable1.FieldByName('Value').AsInteger;
      FDMemTable1.Next;
    end
  finally
    FDMemTable1.Filter := '';
    FDMemTable1.Filtered := False;
    FDMemTable1.EnableControls;
  end;
   Button1.Caption := IntToStr(_ValueSum);
end;

如果这样做,则根本不需要 Button2Click 方法。

如评论中所述,您可以使用 TBookMark 在循环之前记录您在数据集中的位置,然后 return 记录您在循环之后的位置,如

var
  _ValueSum : Integer;
  BM : TBookMark;
begin
  _ValueSum := 0;

  BM := FDMemTable.GetBookMark;

  FDMemTable1.Filter := 'ID like ' + QuotedStr('A%');

  try
    [etc]
  finally
    FDMemTable1.Filter := '';
    FDMemTable1.Filtered := False;
    FDMemTable1.GotoBookMark(BM);
    FDMemTable1.FeeBookMark(BM);
    FDMemTable1.EnableControls;
  end;

顺便说一句,您可以使用 InsertRecord 中的方法来节省一些输入并获得更简洁的代码

FDMemTable1.InsertRecord(['A1', 1]);
FDMemTable1.InsertRecord(['B1', 2]);
FDMemTable1.InsertRecord(['A2', 3]);
FDMemTable1.InsertRecord(['B2', 4]);

顺便说一句#2:使用 FindKey 的时间是在设置要查找的键之后,通过调用 SetKey 使用而不是设置键值。

对于数据集的普通导航,请使用标准导航方法,例如NextPriorFirstLastMoveBy

FireDAC 还有另一个有趣的选项——聚合:

procedure TForm1.Button1Click(Sender: TObject);
begin
  FDMemTable1.Aggregates.Clear;
  with FDMemTable1.Aggregates.Add do
  begin
    Name := 'SUM';
    Expression := 'sum(iif(ID like ''A%'', value, 0))';
    Active := True;
  end;
  FDMemTable1.AggregatesActive := True;
  FDMemTable1.Refresh;
  Button1.Caption := VarToStr(FDMemTable1.Aggregates[0].Value));
end;