【问题标题】:How can I do this in Delphi?我怎么能在德尔福做到这一点?
【发布时间】:2009-03-09 20:07:00
【问题描述】:

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

在 BDE 下,如果查询是 Open 并且您调用了“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;

【问题讨论】:

    标签: delphi ado bde


    【解决方案1】:

    这不是 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 应该会给您想要的行为。

    【讨论】:

    • 你是对的,我也看过那个代码。但是,像 TQuery 一样修改我的自定义 ADO SetQuery 并没有帮助(我使用完全相同的代码,除了“关闭”而不是“断开连接”)
    【解决方案2】:

    问题是当您发出清除时您的数据集是打开的。对于 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...即使有几百个实例。

    【讨论】:

    • 是的,我知道...但我希望避免将此行添加到代码中的 100 个现有位置。
    【解决方案3】:

    当然,您可以创建一个覆盖 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 使用。

    【讨论】:

    • 好吧,也许我在这里很密集.. 但是 Clear 方法在 SQL 属性上,它是 TWideStrings 类型的。如何覆盖 TADOQuery 后代的 TWideStrings.Clear 方法?也许这应该是我真正的问题....
    • 抱歉,误读了您的问题。不,您不能覆盖清除。但话又说回来,它不应该给出错误消息,所以还有其他问题。
    【解决方案4】:

    你用的是什么版本的delphi?我试图复制它,但它在 delphi 2009 中运行良好 - 没有报告错误并且它返回我期望的数据。

    谢谢 不要

    【讨论】:

    • 我使用的是 Delphi 2007 和 Delphi 5,它们都表现出同样的问题。我觉得有趣的是 D2009 没有同样的问题。
    【解决方案5】:

    更新

    我通过编写一个小实用程序来自动更新我们所有的源代码来实现 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;
    

    【讨论】:

      【解决方案6】:

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

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

      HTH。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-07-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-11-09
        • 2011-11-28
        • 1970-01-01
        相关资源
        最近更新 更多