断开连接后,在状态dsInsert中恢复TADOQuery

时间:2016-07-21 09:09:03

标签: delphi ado adodb

我们使用带有显式连接的Delphi TADOQuery进行插入。

要点: 当查询处于状态dsInsert时连接丢失时,查询似乎进入与基础ADO记录集相关的不一致状态。因此,即使已重新建立连接,也无法再使用该查询。

详细信息:

假设以下简化步骤:

  quTest.Connection:= ADOConnection1;
  quTest.Open;
  quTest.Insert;
  //Simulate lost connection
  ADOConnection1.Close;

  try
    //quTest.State is still dsInsert
    quTest.Post; //Throws 'Operation is not allowed when the object is closed'. This is the expected beavior.
  except
    //Reconnect (simplified algorithm)
    ADOConnection1.Connected:= true;
  end;

  //quTest.State is still dsInsert
  //So far, so good.
  //Now let's close or abort or somehow reset quTest so that we can use it again. How? 
  quTest.Close //throws 'Operation is not allowed when the object is closed'

问题是,在上面的代码示例结束时,quTest仍处于状态dsInsert,但基础ADO记录集已断开连接。 任何关闭或以某种方式重置quTest的尝试都会失败,并出现异常'当对象关闭时不允许操作'。

请注意,我们的目标不是继续初始插入操作。我们只想将查询恢复到我们可以打开并再次使用它的状态。

这可能吗?

由于quTest是具有设计时字段绑定的数据模块的一部分,因此我们无法轻松释放损坏的查询并创建新的查询实例。

编辑: 当然,断开连接的模拟并不太现实。 但是,比较生产错误和测试样本的堆栈跟踪,我们发现测试已经足够好了。

Production stack trace:

================================================================================
Exception class  : EOleException
Exception message: Operation is not allowed when the object is closed
EOleException.ErrorCode : -2146824584
================================================================================
[008B9BD7] Data.Win.ADODB.TCustomADODataSet.InternalGotoBookmark + $17
(0000E290) [0040F290]
[008B9BD7] Data.Win.ADODB.TCustomADODataSet.InternalGotoBookmark + $17
[008B9BF4] Data.Win.ADODB.TCustomADODataSet.InternalSetToRecord + $14
[0081EEBE] Data.DB.TDataSet.InternalSetToRecord + $2
[0081D576] Data.DB.TDataSet.SetCurrentRecord + $62
[0081D9A4] Data.DB.TDataSet.UpdateCursorPos + $10
[0081E378] Data.DB.TDataSet.Cancel + $68
[0081AA49] Data.DB.TDataSet.SetActive + $AD
[0081A841] Data.DB.TDataSet.Close + $9


Test case stack trace:

Data.Win.ADODB.TCustomADODataSet.InternalFirst
Data.DB.TDataSet.SetCurrentRecord(0)
Data.DB.TDataSet.UpdateCursorPos
Data.DB.TDataSet.Cancel
Data.DB.TDataSet.SetActive(???)
Data.DB.TDataSet.Close

实际上,由于查询状态仍为dsInsert,因此尝试.Cancel,导致后续调用ADO记录集失败。

procedure TDataSet.SetActive(Value: Boolean);
begin
...
        if State in dsEditModes then Cancel;
...
end;

编辑2: 问题不容易重现,因为它似乎与数据有关。 这就是我创建控制台测试程序的原因。 请运行测试程序两次并更改主块中的测试用例。 我机器上测试的输出如下所示。

控制台测试程序:

program Project2;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  Data.DB,
  Data.Win.ADODB,
  ActiveX;

procedure Setup(aConnection: TADOConnection; aEmpty: Boolean);
var
  query: TADOQuery;
begin
  query:= TADOQuery.Create(nil);
  try
    query.Connection:= aConnection;

    //Create test table
    try
      query.SQL.Add('create table test3 (a int)');
      query.ExecSQL;
      WriteLn('Table created.');
    except
      on e: Exception do
        Writeln(e.Message);
    end;

    //Clear test table
    query.SQL.Clear;
    query.SQL.Add('delete test3');
    query.ExecSQL;

    if not aEmpty then begin
      //Create a row
      query.SQL.Clear;
      query.SQL.Add('insert into test3 values (0)');
      query.ExecSQL;
    end;
  finally
    query.Free;
  end;
end;

var
  con: TADOConnection;
  query: TADOQuery;
begin
  CoInitialize(nil);
  try
    con:= TADOConnection.Create(nil);
    query:= TADOQuery.Create(nil);
    try
      con.ConnectionString:= 'Provider=SQLOLEDB.1;Persist Security Info=False;Integrated Security=SSPI;Data Source=10.0.0.11,1433;Initial Catalog=TestDB';
      con.Connected:= true;

      //Test case 1: With data
      Setup(con, false);

      //Test case 2: No data
      //Setup(con, true);

      query.Connection:= con;
      query.SQL.Add('select * from test3');
      query.Open;
      query.Insert;
      con.Close;
      WriteLn('query.Active: ' + BoolToStr(query.Active));
      WriteLn('query.State: ' + IntToStr(Ord(query.State)));
      query.Close;
      WriteLn('Test ran without exception.');
    except
      on E: Exception do
        Writeln('Exception: ' + E.ClassName, ': ', E.Message);
    end;
  finally
    ReadLn;

    query.Free;
    con.Free;
  end;
end.

测试环境:

  • Delphi 10 Seattle Version 23.0.21418.4207
  • 控制台测试程序平台:Win32
  • Microsoft SQL Server 2008 R2(SP1) - 10.50.2550.0(X64)

经过测试:

  • IDE中的Windows 8.1 Pro
  • Windows 8.1 Pro
  • Windows Server 2008 R2 Standard,6.1.7601 SP1 Build 7601
  • Windows Server 2008 R2标准版

测试用例1的输出:

There is already an object named 'test3' in the database
query.Active: 0
query.State: 0
Test ran without exception.

测试用例2的输出:

There is already an object named 'test3' in the database
query.Active: -1
query.State: 3
Exception: EOleException: Operation is not allowed when the object is closed

2 个答案:

答案 0 :(得分:3)

我不喜欢发布一个实际上没有回答问题的答案,但是 在这种情况下,我认为我应该因为我根本无法重现你所说的内容 您对quTest状态的评论。也许是分歧 我的结果和你的结果之间是由于代码或对象属性的某些部分 这些都没有包含在您的问题中。

请尝试这个(我在D7和西雅图测试过):

启动一个新项目并在表单上删除TAdoConnection和TAdoQuery。 仅进行下面DFM提取中显示的属性更改;

设置下面显示的代码提取中显示的事件处理程序。

BeforeCloseBeforeCancel处理程序中放置断点,在

上放置一个断点
quTest.Post

然后编译,运行并单击Button1。

我得到的内容如下:

  1. BP on BeforeClose旅行。

  2. BP在BeforeCancel之旅。

  3. BP on quTest.Post旅行。

  4. 在步骤3,quTest的状态为dsInactive,其Active属性为False。 这些值以及事先调用Before ...事件的事实是 正是我所期待的,因为调用AdoConnection.Close关闭了 使用它作为Connection的数据集。

    所以,我认为如果你的应用得到不同的结果,你需要解释 为什么因为我认为我已经证明测试项目没有展示出来 你报告的行为。

    更新2

    1. 应OP的要求,我在表格中添加了int列'a',并添加了相应的ParameterquTest并添加了

      quTest.Parameters.ParamByName('a')。值:= 0;

    2. 在我致电quTest.Open之前

      。当quTest.Post上的BP跳闸时,这会对quTest的State和Active属性产生 no 差异:它们仍然分别是dsInactive和False。

      1. 由于OP已经表示他希望能够在中止插入后继续使用quTest,我将quTest.Post;替换为quTest.Open;。之后,一旦发生Inssert异常,我可以继续使用quTest而没有任何明显的问题 - 我可以手动删除,插入和编辑,这些都正确地传递回服务器,这样当应用程序是重新运行,这些变化持续存在。
      2. 更新3 。 OP似乎有些怀疑,调用AdoConnection1.Close会导致quTest被关闭。 确实。要验证这一点,请在Form1.quTest.RecordSetState上加注,并将应用程序运行到AdoConnection1.Close。然后,跟踪到该呼叫。您会发现TCustomConnection.SetConnected调用DoDisconnect 它调用ConnectionObject.Close。这将quTest.RecordSetState设置为stClosed,以便在执行TAdoConnection.Disconnect时

        for I := 0 to DataSetCount - 1 do
            with DataSets[I] do
              if stClosed in RecordsetState then Close;
        

        quTest已关闭。

        示例代码

          TForm1 = class(TForm)
            ADOConnection1: TADOConnection;
            quTest: TADOQuery;
            Button1: TButton;
            procedure FormCreate(Sender: TObject);
            procedure Button1Click(Sender: TObject);
            procedure quTestBeforeCancel(DataSet: TDataSet);
            procedure quTestBeforeClose(DataSet: TDataSet);
          public
            { Public declarations }
            procedure TestReconnect;
          end;
        
        [...]
        
        procedure TForm1.FormCreate(Sender: TObject);
        begin
          quTest.Open;
        end;
        
        procedure TForm1.Button1Click(Sender: TObject);
        begin
          TestReconnect;
        end;
        
        procedure TForm1.quTestBeforeCancel(DataSet: TDataSet);
        begin
          Caption := 'Before Cancel';
        end;
        
        procedure TForm1.quTestBeforeClose(DataSet: TDataSet);
        begin
          Caption := 'Before close';
        end;
        
        procedure TForm1.TestReconnect;
        begin
          quTest.Connection:= ADOConnection1;
          quTest.Open;
          quTest.Insert;
          //quTest.FieldByName('Name').AsString := 'yyyy'; added by MA
        
          //Simulate lost connection
          ADOConnection1.Close;
        
          try
            quTest.Post; //Throws 'Operation is not allowed when the object is closed'
          except
            //Reconnect (simplified algorithm)
            ADOConnection1.Connected:= true;
            quTest.Post;
          end;
        end;
        
        end.
        

        部分DFM

        object ADOConnection1: TADOConnection
          Connected = True
          ConnectionString =
            'Provider=SQLOLEDB.1;Persist Security Info=False;User ID=sa;Initia' +
            'l Catalog=MATest;Data Source=MAI7'
          Provider = 'SQLOLEDB.1'
          Left = 24
          Top = 24
        end
        object quTest: TADOQuery
          Connection = ADOConnection1
          CursorType = ctStatic
          BeforeClose = quTestBeforeClose
          BeforeCancel = quTestBeforeCancel
          Parameters = <>
          SQL.Strings = (
            'Select * from TestTable')
          Left = 64
          Top = 24
        end
        

        更新1 以下代码允许完成待处理的插入内容 在except区块。请注意在except块中调用quTest.Post缺席

        procedure TForm1.TestReconnect;
        const
          SaveFileName = 'C:\Temp\testdata.xml';
        begin
          quTest.Connection:= ADOConnection1;
          quTest.Open;
          quTest.Insert;
          quTest.FieldByName('Name').AsString := 'yyyy';
          quTest.SaveToFile(SaveFileName, pfXML);
          //Simulate lost connection
          ADOConnection1.Close;
        
          try
            quTest.Post; //Throws 'Operation is not allowed when the object is closed'
          except
            //Reconnect (simplified algorithm)
            ADOConnection1.Connected:= true;
            quTest.LoadFromFile(SaveFileName);
          end;
        end;
        

答案 1 :(得分:0)

观察到的行为的原因是Delphi XE6中或之前的变化,我认为这是一个错误。

https://quality.embarcadero.com/browse/RSP-15545

要点:

  • Delphi 2007和Delphi XE中不会出现此问题。
  • Delphi 10.1中出现问题
  • 在TDataSet.SetActive中的XE6中或之前引入了有问题的代码更改,其中添加了对Cancel的新调用。
  • 此调用在描述的方案中失败,导致所描述的效果。