如何使用 TAdoTable.Filter 过滤 .mdb Access 数据库

How to filter a .mdb Access Database using TAdoTable.Filter

我有一个 TADOConnection 和一个 TADOTable,我想过滤 table 以查找包含或以用户键入 TEdit 中的字符开头的职业在表格上。这是我目前使用的代码:

procedure TfrmProfessions.Filter;
begin
  if edtSearch.text = '' then
  begin
    dmPAT.ADOTable1.filtered := false;
  end
  else
  begin
    with dmPAT.ADOTable1 do
    begin
      filtered := false;
      Filter := 'Profession LIKE ' + quotedstr(edtSearch.text + '%');
      filtered := true;
    end;
  end;
end;

我在这一行遇到访问冲突,我不知道如何解决:

dmPAT.ADOTable1.filtered := false;

我怀疑这是因为,在表单的 OnCreate 事件中,我清除了 TEdit 并且它在创建 TADOTable 对象之前调用了这个 Filter procedure , 虽然我不确定。

我遇到的另一个错误是 Cannot convert type(Null) to type(String)。当我设置过滤器时我得到了这个,它过滤了,但是没有匹配过滤器的记录。我该如何解决?

尝试以下操作:

在程序开始时 TfrmProfessions.Filter,添加这些行

Caption := dmPat.AdoTable1.FieldByName('Profession').ClassName;  // the purpose of this is 
//  so that you can tell at a glance what field type your Professions field is. which could be important

Assert(Assigned(dmPat));  //  Assert generates an exception if its argument is false
//  so this checks that dmPat has been created
Assert(Assigned(dmPat.AdoTable1));  //  checks that dmPat.AdoTable1 has been created.
Assert(dmPat.AdoTable1.Active);  // checks that the table is open

这么多应该可以解决所有与尚未创建的数据库对象有关的问题。

更新 我从你的评论中了解到你已经解决了你的异常 问题并期待阅读你是如何做到的。与此同时,我一直 编写以下内容并决定 post 它可能会给您和其他人一些见解 了解如何着手调查您报告的问题。

我在 D10.4.2 中启动了一个新的 VCL,并在主窗体中添加了一个 TAdoConnection、一个 TAdoTable、一个 TADoCommand、一个 TDataSource 连接到 AdoTable1,一个 TDBGrid 和 TDBNavigator 连接到 DataSource 和一个用于搜索文本的 TEdit, 加上几个 TButtons 来调用下面的例程。我没有打扰 将 TAdoTable 等放入 TDataModule 中,因为我已经提到了如何处理它。

然后我创建了一个测试 Access 数据库,其中包含一个名为 AName 的文本字段和一个名为 ANumber 的数字字段,使用 以下代码

procedure TForm1.Button3Click(Sender: TObject);
begin
  if AdoTable1.Active then
    AdoTable1.Close;
  AdoCommand1.CommandText :=

{.$define DropTable}
{$ifdef DropTable}
  'Drop table MATest';
{$else}
  'CREATE TABLE MATest (AName Char(12) , ANumber NUMBER)';
{$endif}

  AdoCommand1.Execute;
end;

$ifdef 这样我就可以轻松地放下 table 并重新开始。

网络中的“后天智慧” groups 似乎是要在 Delphi 代码中创建 Access table,您需要使用 AdoX ActiveX 库,它独立于 Ado 访问组件。然而,TAdoCommand 完全有能力做到这一点。

然后,我使用以下过程向其中添加了一些记录:

procedure TForm1.InsertData;
begin
  ADoTable1.Open;

  AdoTable1.InsertRecord(['aaa']);
  AdoTable1.InsertRecord(['abb']);
  AdoTable1.InsertRecord(['bbb']);
end;

请注意,这里故意不为第二个 ANumber 字段指定值,因此在数据库文件中,ANumber 字段应该接收 Access 用于缺失值的任何值(我希望 为 Null,但无论如何。

顺便说一句,在调查问题时,我总是在代码中创建新记录,以便 值在程序的每个 运行 中都保持不变,这样我就不用考虑要输入的数据了。

然后我为 AdoTable1 添加了这个 AfterScroll 事件

procedure TForm1.ADOTable1AfterScroll(DataSet: TDataSet);
var
  F : TField;
  S : String;
begin
  F := AdoTable1.Fields[0];
  S := F.AsString;
  F := AdoTable1.Fields[1];
  S := S + ' / ' + F.AsString;
  Caption := S;
end;

这样做的目的是强制重新评估两者的字符串表示形式 字段以查看在 table 周围滚动是否引发了您遇到的异常。 结果:没有遇到异常。

然后我添加了以下代码来设置过滤

procedure TForm1.edtSearchChange(Sender: TObject);
begin
  UpdateFilter;
end;

procedure TForm1.UpdateFilter;
begin
  Assert(AdoTable1.Active);  // checks that the table is open
  AdoTable1.Filtered := False;  //  we should turn filtering off whether or not
  //  edSearchText.Text is blank or not

  if Trim(edtSearch.Text) <> '' then begin  //  Trim() removes leading and trailing blanks
    AdoTable1.Filter := 'AName LIKE ' + quotedstr('%' + edtSearch.Text + '%');
    AdoTable1.Filtered := True;
  end;
end;

而且效果很好。 aa 的过滤器匹配前两行,b 匹配第二行和第三行。 ADO Filter 属性 记录在案 here.

QED。我希望这个示例表明,通过逐步构建测试项目而不是尝试调试已完成的项目,可以更容易地调查您遇到的问题。

调查这一切,我注意到一些关于 ADO 的东西,我前段时间注意到:Delphi TAdo 组件通过 Windows 中的 MDAC(Microsoft 数据访问组件)层访问数据库并在某些数据库之后操作以在 MDAC 层中生成异常的方式失败,该层的行为不稳定,直到 Windows 重新启动。我很确定我 运行 在这里,因为在我尝试成功过滤之后,发生了一些错误或其他错误,之后我根本无法让 Ado 过滤工作(它总是产生空结果)直到我重启了Windows。之后,它恢复正常工作。

更新 2

有一个问题挥之不去。为什么如果 TDBEdit 连接到我的 ANumber 字段(或您的 Hours 字段),它工作正常,但如果我们 t运行sfer 字段值 手动输入 TEdit 或 TSpinEdit,我们得到字符串转换异常?我得到 它与此代码

procedure TForm1.DataSource1DataChange(Sender: TObject; Field: TField);
begin
  sedNumber.Text := AdoTable1.FieldByName('ANumber').Value;
end;

答案在Vcl.DBCtrls单元TDBedit的源代码中找到:

procedure TDBEdit.DataChange(Sender: TObject);
begin
  if FDataLink.Field <> nil then
  begin
    [...]
    if FFocused and FDataLink.CanModify then
      Text := FDataLink.Field.Text
    else
    begin
      EditText := FDataLink.Field.DisplayText;
      if FDataLink.Editing and FDataLink.FModified then
        Modified := True;
    end;
  end [...]

请注意,这不会访问该字段的 Value,而是它的 TextDisplayText 属性 这就是答案。因此,一个比你的更简洁的修复方法就是简单地做

procedure TForm1.DataSource1DataChange(Sender: TObject; Field: TField);
begin
  sedNumber.Text := AdoTable1.FieldByName('ANumber').Text;
end;

并且在没有任何显式 Null 检查的情况下使异常静音。它之所以有效,是因为

procedure TFloatField.GetText

在 Data.DB 中显式 returns 空字符串作为 Text 字段包含空值时的值。

所以我追查到了问题所在。在我的 DataModuleOnDataChange 事件中,我调用了这个过程:

procedure TdmPAT.DataSource1DataChange(Sender: TObject; Field: TField);
begin
  frmProfessions.Show_Record_Values;
end;

这是带有 DBGrid:

形式的程序
procedure TfrmProfessions.Show_Record_Values;
begin
  with dmPAT.ADOTable1 do
  begin
    sedPK.value := FieldByName('PK').value;
    sedHours.value := FieldByName('Hours').value;

    edtProfession.text := fields[1].value;
    edtCost.text := floattostrf(FieldByName('Cost').AsFloat, ffcurrency, 12, 2);

    if FieldByName('Popular').asboolean then
      rgpPopular.ItemIndex := 0
    else
      rgpPopular.ItemIndex := 1;
  end;
end;

现在,一旦我过滤了 DBGrid,问题就来了。它不是过滤器本身,但它会导致记录显示为 Null 值。然后,当我尝试在编辑中显示活动记录(空记录)时,delphi 无法在以下行将 Null 转换为字符串:sedHours.value:=FieldByName('Hours').value;

这就是我收到错误的原因。 XD 一些 try 子句应该可以解决问题。 我重做了上面程序的代码,问题解决了

procedure TfrmProfessions.Show_Record_Values;
var
  Profession, Hours, Cost, Popular: Tfield;
begin
  Profession := dmPAT.ADOTable1.fields[1];
  Hours := dmPAT.ADOTable1.fields[2];
  Cost := dmPAT.ADOTable1.fields[3];
  Popular := dmPAT.ADOTable1.fields[4];

  // check if null values
  if Profession.value = Null then
    edtProfession.text := ''
  else
    edtProfession.text := Profession.value;

  if Hours.value = Null then //The Hours Field is an Integer.
    sedHours.value := 0 //sedHours is a TSpinEdit.
  else
    sedHours.value := Hours.value;

  if Cost.value = Null then //The Cost field is Currency in MSAccess.
    edtCost.text := floattostrf(0, ffcurrency, 12, 2)
  else
    edtCost.text := floattostrf(Cost.value, ffcurrency, 12, 2);

  if Popular.value = Null then
  begin
    rgpPopular.ItemIndex := -1;
  end
  else
  begin
    case Popular.asboolean of
      true:
        rgpPopular.ItemIndex := 0;
      false:
        rgpPopular.ItemIndex := 1;
    end;
  end;
end;