【问题标题】:Setting CommandTimeout after Execution causes error (column appears multiple times)执行后设置 CommandTimeout 会导致错误(列出现多次)
【发布时间】:2020-08-26 11:55:08
【问题描述】:

我有一个使用 ADO 组件的 Delphi 项目,目前是 SQL Server 2017。

我注意到 Microsoft has 3 possible providers ,Microsoft 建议对新项目使用“第 3 代”。

  SQLOLEDB (Generation 1) works
  SQLNCLI11 (Generation 2) does NOT work
  MSOLEDBSQL (Generation 3) does NOT work

为了测试“第 3 代”,我将连接字符串中的提供程序从 SQLOLEDB 更改为 MSOLEDBSQL。

但是,我注意到如果在事务中使用 TAdoCommand 添加列,然后设置超时,则会导致问题。执行后设置超时时会发生错误。

这是一个重现问题的示例:

uses
  ADODB;

procedure TForm2.Button1Click(Sender: TObject);
const
  // SQLOLEDB (Generation 1) works
  // SQLNCLI11 (Generation 2) does NOT work
  // MSOLEDBSQL (Generation 3) does NOT work
  SqlServerProvider= 'MSOLEDBSQL';
var
  mConnection: TADoConnection;
  command: TadoCommand;
begin
  mConnection := TAdoConnection.Create(nil);
  try
    mConnection.ConnectionString := 'Provider='+SqlServerProvider+';Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=CORAMASTER_1;Data Source=SHS\HS2017,49010';

    mConnection.KeepConnection := true;
    mConnection.LoginPrompt := false;

    mConnection.Connected := true;

    mConnection.BeginTrans;

    command := TADOCommand.Create(nil);
    try
      command.Connection := mConnection;
      command.ParamCheck := false;
      command.CommandText := 'alter table TESTTABLE add TESTCOLUMN int;';
      command.CommandTimeout := 100;
      command.Execute;

      // If I set "CommandTimeout" here, I get the following error:
      //        Spaltennamen müssen in jeder Tabelle eindeutig sein. Der Spaltenname "..." wurde in der ...-Tabelle mehrmals angegeben.
      //        Translated: Column names must be unique in each table. The column name "..." appears multiple times.
      // The error happens with provider MSOLEDBSQL (Generation 3) and SQLNCLI11 (Generation 2), but SQLOLEDB (Generation 1) works
      command.CommandTimeout := 50; // ERROR!
    finally
      FreeAndNil(command); // Note: In the real project, I am keeping the command object. This is just for the example
    end;

    mConnection.RollbackTrans;

  finally
    FreeAndNil(mConnection);
  end;

end;

我做错了什么?这是 ADO 组件中的错误吗?

编辑:这是使用 Microsoft SQL Server Profiler 的 SQL Server 跟踪日志:

第一代(作品):

第 2 代:

第 3 代:

当我执行第二个“SetTimeout”命令时,SQL 服务器执行命令exec [sys].sp_describe_first_result_set N'alter table TESTTABLE add TESTCOLUMN int;',NULL,1,这显然会导致错误。我不知道为什么会调用 sp_describe_first_result_set 以及如何防止它...

【问题讨论】:

  • 哪个 Delphi 版本? Istr 在 很久 之前(大约 D3/D5 时代)在 Delphi 中设置 ADO 超时存在问题。
  • 我使用的是 Delphi 2007(我知道它很旧,但不幸的是,我依赖无法更新的旧 VCL/框架)
  • 为什么要在命令执行后设置CommandTimeout?
  • 我对那个 Delphi 一无所知 - 但我看到一个连接已创建但从未打开。很难相信mConnection.Connected := true; 实际上打开了连接。没有 CATCH 的 TRY 是没有意义的。
  • @SMor:恐怕你的两个cmets都不正确。在 Delphi 中,将 Connected 属性设置为 True 是打开连接的一种完全有效的方式(实际操作发生在属性的设置器中),它有两个 try 构造,try ... except 用于异常处理和 @987654335 @ 用于资源保护。

标签: sql-server delphi ado


【解决方案1】:

我找到了问题。

微软在Using Connection String Keywords with OLE DB Driver for SQL Server 描述:

使用 FMTONLY

控制在连接到 SQL Server 2012 时如何检索元数据 (11.x) 及更新版本。可能的值为真和假。默认 值为假。

默认情况下,SQL Server 的 OLE DB 驱动程序使用 sp_describe_first_result_set 和 sp_describe_undeclared_pa​​rameters 用于检索元数据的存储过程。这些存储过程有 一些限制(例如,它们在运行时会失败 临时表)。将 Use FMTONLY 设置为 true 会指示驱动程序 改为使用 SET FMTONLY 进行元数据检索。

如果我执行 mConnection.Execute('set fmtonly on') ,那么一切正常。

在分析器屏幕截图(上图)中,我可以看到,第 1 代驱动程序实际上经常执行这些命令。


编辑: 我注意到set fmtonly on 对我不起作用。由于某种原因,各种事情都不起作用,例如所有结果集都是空的。

但是关于最初的问题,我发现了一件非常有趣的事情! 仅当在同一个函数调用中调用 CommandTimeout 时,第二次设置才会崩溃!如果我之后立即调用 CommandTimeout,但在一个新的函数调用中,那么一切正常!

这是一个例子:

procedure CausesBug;
begin
  command.CommandTimeout := 100;
  command.ParamCheck := false;
  command.CommandText := 'alter table TESTTABLE add TESTCOLUMN'+inttostr(Random(10000))+' int;';
  command.Execute;

  command.CommandTimeout := 100; // Crash!
  // This calls: exec [sys].sp_describe_first_result_set N'alter table TESTTABLE add TESTCOLUMN2729 int;',NULL,1
end;

procedure CausesNoBug;
  procedure Test1;
  begin
    command.CommandTimeout := 100;
    command.ParamCheck := false;
    command.CommandText := 'alter table TESTTABLE add TESTCOLUMN'+inttostr(Random(10000))+' int;';
    command.Execute;
  end;
begin
  Test1;
  Test1; // Although CommandTimeout is the first command, everything works!
         // "sp_describe_first_result_set" is NOT called!
  Test1;
  Test1;
end;

还有:

procedure AlsoCausesNoBug;
  procedure Test1;
  begin
    command.ParamCheck := false;
    command.CommandText := 'alter table TESTTABLE add TESTCOLUMN'+inttostr(Random(10000))+' int;';
    command.Execute;
  end;
begin
  command.CommandTimeout := 100;
  Test1;
  command.CommandTimeout := 100;
end;

注意:我还尝试禁用编译器优化,以确保没有问题。

【讨论】:

    猜你喜欢
    • 2011-12-28
    • 2021-11-19
    • 2016-07-05
    • 2015-12-16
    • 1970-01-01
    • 2014-06-05
    • 1970-01-01
    • 1970-01-01
    • 2013-02-14
    相关资源
    最近更新 更多