我们使用带有显式连接的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版本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内部版本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

最佳答案

我不喜欢发布实际上无法回答问题的答案,但是
在这种情况下,我认为我应该这样做,因为我根本无法复制您在
您对quTest状态的评论。也许分歧
我的结果与您的结果之间的差异是由于代码或对象属性的某些部分
不包含在您的问题中。

请尝试以下操作(我已经在D7和Seattle中对其进行了测试):

启动一个新项目,然后在窗体上放置一个TAdoConnection和TAdoQuery。
仅更改下面DFM摘录中显示的属性;

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

将断点放在BeforeCloseBeforeCancel处理程序中,然后在

quTest.Post


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

我得到的如下:


BP的BeforeClose行程。
BP在BeforeCancel行程中。
quTest.Post上的BP。


在步骤3,quTest的状态为dsInactive,其Active属性为False
这些值和Before ...事件被预先调用的事实是
正是由于调用AdoConnection.Close关闭
使用数据集作为其Connection的数据集。

因此,我认为,如果您的应用程序获得不同的结果,则需要说明
为什么,因为我认为我已经证明测试项目没有展示
您举报的行为。

更新2


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

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


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


正如OP所说的那样,他只是希望能够在中断插入后继续使用quTest,因此我在except块中将quTest.Post;替换为quTest.Open;。之后,一旦发生了Inssert异常,我就可以继续使用quTest而没有任何明显的问题-我可以手动执行Deletes,Inserts和Edits,并将它们正确地传递回服务器,以便在重新启动应用程序时运行时,这些更改一直存在。


更新3。OP似乎有些疑问,调用AdoConnection1.Close会导致quTest被关闭。是的为了验证这一点,请在Form1.quTest.RecordSetState上放置手表,并运行appl至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;

关于delphi - 断开连接后,在状态dsInsert中恢复TADOQuery,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/38499796/

10-12 18:40