【问题标题】:Recover TADOQuery in State dsInsert after disconnect断开连接后恢复状态为 dsInsert 的 TADOQuery
【发布时间】:2016-07-21 09:09:03
【问题描述】:

我们使用带有显式插入连接的 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 西雅图版本 23.0.21418.4207
  • 控制台测试程序平台:Win32
  • Microsoft SQL Server 2008 R2 (SP1) - 10.50.2550.0 (X64)

测试日期:

  • IDE 中的 Windows 8.1 Pro
  • Windows 8.1 专业版
  • 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

【问题讨论】:

  • 您遇到的问题是因为您尝试模拟断开连接的方式。当您调用 AdoConnection1.Close 时,这将放弃插入,并关闭 quTest。因此,当您调用 quTest.Post 时,这必然会失败(其状态为 dsInactive)。
  • 打开查询,仅此而已
  • @MartynA 不。就在 quTest.Post 之前,quTest.State 仍然是 dsInsert。我们预计 quTest.Post 会失败。我们想在之后恢复 quTest。
  • @SirRufo quTest.Open 会抛出“当对象关闭时不允许操作”,就像 quTest.Close 或 quTest.Cancel。
  • 我无法复制你所说的内容。

标签: delphi ado adodb


【解决方案1】:

我不喜欢发布实际上并不能回答问题的答案,但是 在这种情况下,我认为我应该这样做,因为我根本无法重现您在 您的 cmets 关于您的 quTest 的状态。或许分歧 我的结果和你的结果之间是由于你的代码或对象属性的某些部分 您的问题中未包含的内容。

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

开始一个新项目并在您的表单上放置一个 TAdoConnection 和 TAdoQuery。 只进行下面 DFM 摘录中显示的属性更改;

设置如下代码摘录中所示的事件处理程序。

BeforeCloseBeforeCancel 处理程序中放置断点,然后一个

quTest.Post

然后编译、运行并点击Button1。

我得到如下:

  1. BeforeClose 行程中的 BP。

  2. 取消前行程中的 BP。

  3. quTest.Post 行程中的 BP。

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

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

更新 2

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

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

在我两次致电quTest.Open 之前。当 quTest.Post 上的 BP 跳闸时,这对 quTest 的 State 和 Active 属性没有区别:它们仍然分别为 dsInactive 和 False。

  1. 正如 OP 所说,他只想在中止插入后继续使用 quTest,我将 except 块中的 quTest.Post; 替换为 quTest.Open;。之后,一旦发生插入异常,我就可以继续使用quTest,而不会出现任何明显问题——我可以手动进行删除、插入和编辑,这些都会正确地传回服务器,这样当应用程序重新启动时运行,这些更改仍然存在。

更新 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;

【讨论】:

  • 我也无法复制 (D5)。如果我在发布之前关闭连接,则会收到异常“数据集未处于编辑或插入模式”。当我尝试打电话给 Post 时。然后我可以重新打开连接并打开数据集。
  • 调用quTest.Post;时是否抛出“对象关闭时不允许操作”
  • 感谢您的努力!我无法重现您的测试样本的问题。我的下一步是找出你的测试项目和我的测试项目之间的区别。再次感谢!
  • 另外,异常块中的quTest.Post; 是否真的发布到数据库?从您的回答中不清楚。
【解决方案2】:

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

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

总结:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-18
    • 1970-01-01
    • 2017-07-14
    • 2019-07-07
    • 1970-01-01
    • 2017-11-30
    相关资源
    最近更新 更多