断开连接后恢复状态 dsInsert 中的 TADOQuery
Recover TADOQuery in State dsInsert after disconnect
我们使用 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 的尝试都会失败,并出现异常 'Operation is not allowed when the object is closed'.
请注意,我们的目标不是继续初始插入操作。我们只想将查询恢复到可以再次打开和使用它的状态。
这可能吗?
由于 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 +
(0000E290) [0040F290]
[008B9BD7] Data.Win.ADODB.TCustomADODataSet.InternalGotoBookmark +
[008B9BF4] Data.Win.ADODB.TCustomADODataSet.InternalSetToRecord +
[0081EEBE] Data.DB.TDataSet.InternalSetToRecord +
[0081D576] Data.DB.TDataSet.SetCurrentRecord +
[0081D9A4] Data.DB.TDataSet.UpdateCursorPos +
[0081E378] Data.DB.TDataSet.Cancel +
[0081AA49] Data.DB.TDataSet.SetActive + $AD
[0081A841] Data.DB.TDataSet.Close +
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
- 微软 SQL 服务器 2008 R2 (SP1) - 10.50.2550.0 (X64)
测试于:
- Windows IDE 中的 8.1 Pro
- Windows 8.1 专业版
- Windows Server 2008 R2 标准版,6.1.7601 SP1 内部版本 7601
- Windows 服务器 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 和西雅图测试过):
开始一个新项目并在您的表单上放置一个 TAdoConnection 和 TAdoQuery。
仅进行下面 DFM 摘录中显示的 属性 更改;
设置下面代码摘录中显示的事件处理程序。
在 BeforeClose
和 BeforeCancel
处理程序中放置断点,并在
中放置一个断点
quTest.Post
然后编译,运行点击Button1。
得到的结果如下:
BeforeClose 行程的 BP。
BeforeCancel 行程的 BP。
quTest.Post 次行程的 BP。
在第3步,quTest的状态是dsInactive
,它的Active
属性是False
。
这些值和预先调用 Before ...
事件的事实是
正是我所期望的,因为调用 AdoConnection.Close
关闭
使用它作为 Connection
.
的数据集
所以,我认为如果你的应用程序得到不同的结果,你需要解释
为什么因为我认为我已经表明一个测试项目没有展示
您报告的行为。
更新 2
应OP的要求,我在table中添加了int
列'a',并在[=中添加了相应的Parameter
25=] 并添加了
quTest.Parameters.ParamByName('a').值:= 0;
在我两次调用 quTest.Open
之前。这使得 no 在 BP on quTest.Post trips 时与 quTest 的 State 和 Active 属性不同:它们仍然分别为 dsInactive 和 False。
- 正如 OP 所说,他只想在中止插入后继续使用 quTest,我将 except 块中的
quTest.Post;
替换为 quTest.Open;
。在那之后,一旦发生 Inssert 异常,我可以继续使用 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
Update 1下面的代码允许完成pending insert
在 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 XE6 或之前的更改,我认为这是一个错误。
https://quality.embarcadero.com/browse/RSP-15545
总结:
- 此问题在 Delphi 2007 和 Delphi XE 中没有出现。
- 问题出现在Delphi10.1
- 在 TDataSet.SetActive 中的 XE6 中或之前引入了有问题的代码更改,其中添加了对 Cancel 的新调用。
- 此调用在描述的场景中失败,导致描述的效果。
我们使用 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 的尝试都会失败,并出现异常 'Operation is not allowed when the object is closed'.
请注意,我们的目标不是继续初始插入操作。我们只想将查询恢复到可以再次打开和使用它的状态。
这可能吗?
由于 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 +
(0000E290) [0040F290]
[008B9BD7] Data.Win.ADODB.TCustomADODataSet.InternalGotoBookmark +
[008B9BF4] Data.Win.ADODB.TCustomADODataSet.InternalSetToRecord +
[0081EEBE] Data.DB.TDataSet.InternalSetToRecord +
[0081D576] Data.DB.TDataSet.SetCurrentRecord +
[0081D9A4] Data.DB.TDataSet.UpdateCursorPos +
[0081E378] Data.DB.TDataSet.Cancel +
[0081AA49] Data.DB.TDataSet.SetActive + $AD
[0081A841] Data.DB.TDataSet.Close +
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
- 微软 SQL 服务器 2008 R2 (SP1) - 10.50.2550.0 (X64)
测试于:
- Windows IDE 中的 8.1 Pro
- Windows 8.1 专业版
- Windows Server 2008 R2 标准版,6.1.7601 SP1 内部版本 7601
- Windows 服务器 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 和西雅图测试过):
开始一个新项目并在您的表单上放置一个 TAdoConnection 和 TAdoQuery。 仅进行下面 DFM 摘录中显示的 属性 更改;
设置下面代码摘录中显示的事件处理程序。
在 BeforeClose
和 BeforeCancel
处理程序中放置断点,并在
quTest.Post
然后编译,运行点击Button1。
得到的结果如下:
BeforeClose 行程的 BP。
BeforeCancel 行程的 BP。
quTest.Post 次行程的 BP。
在第3步,quTest的状态是dsInactive
,它的Active
属性是False
。
这些值和预先调用 Before ...
事件的事实是
正是我所期望的,因为调用 AdoConnection.Close
关闭
使用它作为 Connection
.
所以,我认为如果你的应用程序得到不同的结果,你需要解释 为什么因为我认为我已经表明一个测试项目没有展示 您报告的行为。
更新 2
应OP的要求,我在table中添加了
int
列'a',并在[=中添加了相应的Parameter
25=] 并添加了quTest.Parameters.ParamByName('a').值:= 0;
在我两次调用 quTest.Open
之前。这使得 no 在 BP on quTest.Post trips 时与 quTest 的 State 和 Active 属性不同:它们仍然分别为 dsInactive 和 False。
- 正如 OP 所说,他只想在中止插入后继续使用 quTest,我将 except 块中的
quTest.Post;
替换为quTest.Open;
。在那之后,一旦发生 Inssert 异常,我可以继续使用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
Update 1下面的代码允许完成pending insert
在 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 XE6 或之前的更改,我认为这是一个错误。
https://quality.embarcadero.com/browse/RSP-15545
总结:
- 此问题在 Delphi 2007 和 Delphi XE 中没有出现。
- 问题出现在Delphi10.1
- 在 TDataSet.SetActive 中的 XE6 中或之前引入了有问题的代码更改,其中添加了对 Cancel 的新调用。
- 此调用在描述的场景中失败,导致描述的效果。