我怎样才能在德尔福中做到这一点?

发布于 2024-07-14 17:07:39 字数 2190 浏览 4 评论 0原文

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

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

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

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

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

“.Clear”方法位于 SQL 属性上,该属性的类型为 TWideStrings。 是:如何在 TADOQuery 的后代上重写 TWideStrings.Clear 方法

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;

I am converting an application from BDE to ADO.

Under the BDE, if a query was Open and you called "Sql.Clear", it would automatically close the dataset.

However, this is not the case under TADOQuery, where it will raise an exception "Operation cannot be performed on a closed dataset".

A lot of our legacy code relies on the old BDE behavior, so I get lots of runtime errors from code like the example below.

I want to override the Sql.Clear method of my TADOCustomQuery class, so that it will include a ".Close" command. How can I do that?

The ".Clear" method is on the SQL property, which is of type TWideStrings. My real question is: how can I override the TWideStrings.Clear method on a descendant of TADOQuery?

I have a customized TADOQuery component already, with this for the SQL property:

property SQL: TWideStrings read GetSQL write SetSQL;

Here is some code to demonstrate the problem I'm having:

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;

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

挽袖吟 2024-07-21 17:07:39

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

This was not a feature of the BDE per se. If you look at the source that ships with Delphi you will see that the behavior you described is implemented on TQuery.SQL's SetQuery method:

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;

While TADOQuery's SetQuery is simply:

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

Why Borland/Codegear decided not to implement it the same is beyond me. Implementing TQuery's SetQuery in your custom ADOQuery should give you the behavior you desire.

稚然 2024-07-21 17:07:39

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

The problem is that your dataset is open when you issue the clear. For the ADODataset, the property is wired to update the underlying ADO dataset, and when it changes with the dataset open the exception is raised.

All that you have to do is put a dataset close right before your clear and it will all operate properly.

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

EDIT
As an alternative you could create a new form method named SQLCLEAR which looks like the following:

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

and then do a search and replace for " SQL.Clear" to "SqlClear". But I prefer the method of performing the close in my original answer as it is more consistant and will be much easier to maintain long term. Using a tool like gExperts to find all of the instances of Sql.Clear and insert a qryAdo.Close before it is trivial...even if there are a few hundred instances.

九八野马 2024-07-21 17:07:39

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

最好更改所有查询。 这可能是一些工作,但最终是有回报的。

如果您查看帮助中的 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 使用。

Of course, you can create a subclass of TAdoQuery that overwrites the Clear method. But I think it is a bad practice.

Its better to change all the queries. It is maybe some work but in the end it pays of.

If you look at the example of TAdoQuery in the help:

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;

You see it is a bit more different than the BDE version.

So it is probably best to create an ExecuteSQL function (first for the BDE) and then rewrite it to be used by ADO.

神经暖 2024-07-21 17:07:39

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

谢谢
大学教师

what version of delphi are you using? i tried to replicate this, but it works just fine in delphi 2009 - no errors reported and it returns the data i expect.

thanks
don

一紙繁鸢 2024-07-21 17:07:39

更新

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

Update

I implemented skamradt's solution by writing a small utility to automatically update all our source code. It worked like this:

1 - Recursively get a list of all .PAS files in our project folders

2 - Apply this procedure to all of those files:

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;
金橙橙 2024-07-21 17:07:39

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

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

HTH。

Instead of you I'd do like this: check with someone which has D2009 if the behavior is really fixed as Don says - for ex. send him (or to another which has D2009) a test case. If the behavior in D2009 is fixed then the problem is simple.

Copy the ADODB.pas in your project directory. Modify the file in order to have the desired behavior (eg. change the SetSQL method). Recompile. It should work. This will give you time for an eventual upgrade to D2009 when you can remove the old, customized ADODB.pas from your project.

HTH.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文