1

我正在将应用程序从 BDE 转换为 ADO。

在 BDE 下,如果查询是打开的并且您调用了“Sql.Clear”,它将自动关闭数据集。

但是,在 TADOQuery 下情况并非如此,它会引发异常“无法在关闭的数据集上执行操作”。

我们的很多遗留代码都依赖于旧的 BDE 行为,所以我从代码中得到很多运行时错误,如下例所示。

我想覆盖我的 TADOCustomQuery 类的 Sql.Clear 方法,以便它包含一个“.Close”命令。我怎样才能做到这一点?

“.Clear”方法在 SQL 属性上,该属性是 TWideStrings 类型。我真正的问题是:如何在 TADOQuery 的后代上覆盖 TWideStrings.Clear 方法?

我已经有一个自定义的 TADOQuery 组件,它用于 SQL 属性:

property SQL: TWideStrings read GetSQL write SetSQL;

这是一些代码来演示我遇到的问题:

procedure TForm1.btnBDEDemoClick(Sender: TObject);
var
  qryBDE: TQuery;
begin
  //Both queries complete with no problem
  qryBDE := TQuery.Create(nil);
  try
    with qryBDE do begin
      DatabaseName := 'Test';  //BDE Alias
      Sql.Clear;
      Sql.Add('SELECT SYSDATE AS CURDAT FROM DUAL');
      Open;
      ShowMessage('the current date is: ' + FieldByName('CURDAT').AsString);

      Sql.Clear;  //<<<<<NO ERRORS, WORKS FINE
      Sql.Add('SELECT SYSDATE-1 AS YESDAT FROM DUAL');
      Open;
      ShowMessage('And yesterday was: ' + FieldByName('YESDAT').AsString);
    end;  //with qryBDE
  finally
    FreeAndNil(qryBDE);
  end;  //try-finally
end;

procedure TForm1.btnADODemoClick(Sender: TObject);
const
  c_ConnString = 'Provider=OraOLEDB.Oracle.1;Password=*;'+
    'Persist Security Info=True;User ID=*;Data Source=*';
var
  adoConn: TADOConnection;
  qryADO: TADOQuery;
begin
  //First query completes, but the second one FAILS
  adoConn := TADOConnection.Create(nil);
  qryADO := TADOQuery.Create(nil);
  try
    adoConn.ConnectionString := c_ConnString;
    adoConn.Connected := True;
    with qryADO do begin
      Connection := adoConn;
      Sql.Clear;
      Sql.Add('SELECT SYSDATE AS CURDAT FROM DUAL');
      Open;
      ShowMessage('the current date is: ' + FieldByName('CURDAT').AsString);

      Sql.Clear;//<<<<<<<<===========ERROR AT THIS LINE
      Sql.Add('SELECT SYSDATE-1 AS YESDAT FROM DUAL');
      Open;
      ShowMessage('And yesterday was: ' + FieldByName('YESDAT').AsString);
    end;  //with qryADO
  finally
    FreeAndNil(qryADO);
    FreeAndNil(adoConn);
  end;  //try-finally
end;
4

6 回答 6

6

这不是 BDE 本身的特性。如果您查看 Delphi 附带的源代码,您会发现您描述的行为是在 TQuery.SQL 的 SetQuery 方法上实现的:

procedure TQuery.SetQuery(Value: TStrings);
begin
  if SQL.Text <> Value.Text then
  begin
    Disconnect;
    SQL.BeginUpdate;
    try
      SQL.Assign(Value);
    finally
      SQL.EndUpdate;
    end;
  end;
end;

虽然 TADOQuery 的 SetQuery 很简单:

procedure TADOQuery.SetSQL(const Value: TWideStrings);
begin
  FSQL.Assign(Value);
end;

为什么 Borland/Codegear 决定不采用同样的方法,这超出了我的理解。在您的自定义 ADOQuery 中实现 TQuery 的 SetQuery 应该会给您想要的行为。

于 2009-03-09T23:02:03.353 回答
5

问题是当您发出清除时,您的数据集是打开的。对于 ADODataset,该属性被连接以更新基础 ADO 数据集,并且当它随着数据集的打开而更改时,将引发异常。

您所要做的就是在您清除之前关闭一个数据集,它就会正常运行。

with qryADO do 
  begin      
    Connection := adoConn;      
    Sql.Clear;      
    Sql.Add('SELECT SYSDATE AS CURDAT FROM DUAL');      
    Open;      
    ShowMessage('the current date is: ' + FieldByName('CURDAT').AsString);
    qryADO.close; // <=== line added to close the database first.
    Sql.Clear;     
    Sql.Add('SELECT SYSDATE-1 AS YESDAT FROM DUAL');      
    Open;      
    ShowMessage('And yesterday was: ' + FieldByName('YESDAT').AsString);    
  end;  //with qryADO

编辑 作为替代方案,您可以创建一个名为 SQLCLEAR 的新表单方法,如下所示:

function TYourFormOrDataModule.SqlClear;
begin
  qryAdo.Close;
  qryAdo.Sql.Clear;
  qryBde.Sql.Clear;
end;

然后搜索并将“SQL.Clear”替换为“SqlClear”。但我更喜欢在我的原始答案中执行关闭的方法,因为它更一致并且更容易长期维护。使用像 gExperts 这样的工具来查找 Sql.Clear 的所有实例,并在它之前插入一个 qryAdo.Close ......即使有几百个实例。

于 2009-03-09T22:10:55.550 回答
2

当然,您可以创建一个覆盖 Clear 方法的 TAdoQuery 子类。但我认为这是一种不好的做法。

最好更改所有查询。这可能是一些工作,但最终它会付出代价。

如果您查看帮助中的 TAdoQuery 示例:

ADOQuery := TADOQuery.Create(Self);
ADOQuery.Connection := ADOConn;
ADOQuery.SQL.Add(SQLStr);

{ Update the parameter that was parsed from the SQL query: AnId }
Param := ADOQuery.Parameters.ParamByName('AnId');
Param.DataType := ftInteger;
Param.Value := 1;

{ Set the query to Prepared - will improve performance }
ADOQuery.Prepared := true;

try
  ADOQuery.Active := True;
except
  on e: EADOError do
  begin
    MessageDlg('Error while doing query', mtError,
                [mbOK], 0);

    Exit;
  end;
end;

您会发现它与 BDE 版本有点不同。

因此,最好创建一个 ExecuteSQL 函数(首先用于 BDE),然后重写它以供 ADO 使用。

于 2009-03-09T20:14:44.190 回答
2

你用的是什么版本的delphi?我试图复制它,但它在 delphi 2009 中工作得很好——没有报告错误,它返回了我期望的数据。

谢谢唐

于 2009-03-09T20:18:33.700 回答
2

更新

我通过编写一个小实用程序来自动更新我们所有的源代码来实现 skamradt 的解决方案。它是这样工作的:

1 - 递归获取项目文件夹中所有 .PAS 文件的列表

2 - 将此过程应用于所有这些文件:

procedure ApplyChange(filename: string);
const
  c_FindThis = 'SQL.CLEAR';
var
  inputFile, outputFile: TStringList;
  i, postn, offset: integer;
  newline: string;
begin
  inputFile := TStringList.Create;
  outputFile := TStringList.Create;
  offset := 0;
  try
    inputFile.LoadFromFile(filename);
    outputFile.Assign(inputFile);  //default: they are the same

    for i := 0 to inputFile.Count - 1 do begin
      {
      whenever you find a "Sql.Clear", place a new line before it,
      which consists of everything up to the "Sql.Clear" (which may
      just be whitespace), plus the "Close" command.
      //}
      postn := Pos(c_FindThis,Uppercase(inputFile[i]));
      if (0 < postn) then begin
        newline := Copy(inputFile[i],1,postn-1) + 'Close;';
        outputFile.Insert(i+offset,newline);
        Inc(offset);
      end;
    end;

    //overwrite the existing file with the revised one
    outputFile.SaveToFile(filename);
  finally
    FreeAndNil(inputFile);
    FreeAndNil(outputFile);
  end;  //try-finally
end;
于 2009-03-10T16:00:57.007 回答
1

而不是你,我会这样做:与拥有 D2009 的人核实行为是否真的如 Don 所说的那样修复 - 例如。给他(或另一个有 D2009 的人)一个测试用例。如果 D2009 中的行为得到修复,那么问题就很简单了。

复制项目目录中的 ADODB.pas。修改文件以获得所需的行为(例如,更改 SetSQL 方法)。重新编译。它应该工作。当您可以从项目中删除旧的自定义 ADODB.pas 时,这将为您最终升级到 D2009 提供时间。

HTH。

于 2009-03-10T06:21:49.660 回答