Delphi - 为什么我会收到此访问冲突? ADOQuery参数是否有限制?

时间:2015-05-18 14:14:23

标签: delphi access-violation tadoquery

我有这个代码返回访问冲突('访问冲突,地址为74417E44,位于模块' sqloledb.dll'。阅读地址786E3552')我可以' t确定问题所在。我唯一的猜测是ADOQuery对我们可以传递的参数数量有限制。 代码如下:

With qryInsert do
  begin
    Active := False;
    Close;
    Sql.Clear;
    Sql.Add('Insert Into MyTable(ColumnOne, ');
    Sql.Add('             ColumnTwo,           ');
    Sql.Add('             ColumnThree,         ');
    Sql.Add('             ColumnFour,           ');
    Sql.Add('             ColumnFive,          ');
    Sql.Add('             ColumnSix,        ');
    Sql.Add('             ColumnSeven,        ');
    Sql.Add('             ColumnEight,     ');
    Sql.Add('             ColumnNine,       ');
    Sql.Add('             ColumnTen,       ');
    Sql.Add('             ColumnEleven,     ');
    Sql.Add('             ColumnTwelve,   ');
    if qrySelect.FieldByName('ColumnTwelve').AsSTring = 'Y' then
    begin
      Sql.Add('           ColumnThirteen,   ');
      Sql.Add('           ColumnFourteen,   ');
      Sql.Add('           ColumnFifteen,   ');
    end;
    Sql.Add('             ColumnSixteen,   ');
    if qrySelect.FieldByName('ColumnSixteen').AsSTring = 'Y' then
    begin
      Sql.Add('           ColumnSeventeen,         ');
      Sql.Add('           ColumnEighteen,         ');
      Sql.Add('           ColumnNineteen,         ');
    end;
    if qrySelect.FieldByName('ColumnTwenty').AsSTring = 'Y' then
    begin
      Sql.Add('           ColumnTwenty,  ');
      Sql.Add('           ColumnTwentyOne,        ');
      Sql.Add('           ColumnTwentyTwo,        ');
      Sql.Add('           ColumnTwentyThree,        ');
    end
    else
      Sql.Add('           ColumnTwenty,  ');
    Sql.Add('             ColumnTwentyFour) ');
    Sql.Add('Values(:ColumnOne, :ColumnTwo, :ColumnThree, :ColumnFour, ');
    Sql.Add('       :ColumnFive, ' + dateDB + ', :ColumnSeven,          ');
    Sql.Add('       :ColumnEight, :ColumnNine, :ColumnTen, ');
    Sql.Add('       :ColumnEleven,                                    ');
    Sql.Add('       :ColumnTwelve,                                    ');
    if qrySelect.FieldByName('ColumnTwelve').AsSTring = 'Y' then
      Sql.Add('     :ColumnThirteen, :ColumnFourteen, :ColumnFifteen,              ');
    Sql.Add('       :ColumnSixteen,                                      ');
    if qrySelect.FieldByName('ColumnSixteen').AsSTring = 'Y' then
      Sql.Add('     :ColumnSeventeen, :ColumnEighteen, :ColumnNineteen,                 ');
    if qrySelect.FieldByName('ColumnTwenty').AsSTring = 'S' then
    begin
      Sql.Add('   :ColumnTwenty,                                      ');
      Sql.Add('   :ColumnTwentyOne, :ColumnTwentyTwo, :ColumnTwentyThree,                ');
    end
    else
      Sql.Add('   :ColumnTwenty,                                      ');
    Sql.Add('     :ColumnTwentyFour)                                  ');
    {And then for all the parameteres, pass the value}
    Parameters.ParamByName('ColumnOne').Value := varColumnOne;
    ...
    Parameters.ParamByName('ColumnTwentyFour').Value := varColumnTwentyFour;
    ExecSQL;
  end;

我在这一行得到错误:

Sql.Add('       :ColumnTwelve,                                    ');

这是我的insert语句中的第11个参数。 如果我评论这一行,我会在下一个参数中得到错误。 如果我像这样直接输入值:

Sql.Add('     ' + varColumnTwelve + ',                            ');

它工作正常,但我在下一个参数中得到错误。

所以它让我想知道:ADOQuery是否限制了它可以处理的参数数量?或者,如果这不是真正的问题,是否有人知道如何解决这个问题?

注意:

  • 我使用的是Delphi 7和Windows 8.1。

  • 调试时只显示AV(并且始终显示),如果我通过其" .exe"直接执行应用程序,它永远不会出现。

  • 如果我一直按下" Run"出现错误后,它会显示越来越多的AV(我认为AV的数量与第10个之后添加的参数数量相同),直到应用程序继续正常运行。

  • 屏幕上出现所有AV后,插入工作。我只是想明白为什么一切看起来都没有这个错误。

3 个答案:

答案 0 :(得分:6)

更改 TADOQuery 的SQL属性会导致 TADOQuery 响应该更改,将修改后的SQL重新应用于内部ADO组件对象以及重新解析SQL以识别任何参数。

因此,建议不要以这种方式逐步修改SQL。除了其他任何东西之外,在完全组装之前,一遍又一遍地应用和解析SQL是非常低效的。

在这种情况下,当你到达添加第11个参数时,SQL已被应用并解析了28次!

然后在 SQLOLEDB.DLL 中发生的AV的事实表明,无论发生什么问题都是由于SQL的更改应用于内部ADO对象而不是VCL处理以识别参数等。因此,您没有太多能够解决问题的方法。你能做的最好就是避免它。

您可以在修改SQL时设置 ParamCheck:= FALSE 来消除部分处理。这将阻止VCL尝试重新解析修改后的SQL以识别参数。但是,它不会阻止SQL重新应用于基础ADO组件以响应每次更改。

作为诊断练习,您可以在修改SQL时尝试设置 ParamCheck:= FALSE 。完成后,调用 Parameters.Refresh 方法以确保更新参数集合以反映已完成的SQL:

qryInsert.ParamCheck := FALSE;
qryInsert.SQL.Add(..);
qryInsert.SQL.Add(..);
qryInsert.SQL.Add(..);
qryInsert.SQL.Add(..);

qryInsert.Parameters.Refresh;

注意: 将ParamCheck设置为FALSE,您必须在尝试设置任何参数值之前调用 Parameters.Refresh ,否则参数将不存在在参数集合中!

如果此更改后AV仍然出现,那么这更强烈地表明内部ADO组件在响应SQL的重复更改时表现不佳的一些问题,可能是由于未能正确处理不完整(语法不正确) SQL。

但是,您可以通过以下两种方式之一避免触发完全的更改机制。

最简单的方法是在构建SQL的代码周围的 TADOQuery SQL字符串列表中使用 BeginUpdate / EndUpdate

qryInsert.SQL.BeginUpdate;
try
  qryInsert.SQL.Add(..);
  qryInsert.SQL.Add(..);
  qryInsert.SQL.Add(..);

finally
  qryInsert.SQL.EndUpdate;
end;

这样可以抑制ADO查询对象中的内部 OnChange 事件,直到调用 EndUpdate ,此时SQL将应用于内部ADO对象和查询对象的参数已更新。

或者,您可以将SQL组装在一个完全独立的字符串列表中,然后将其应用于 TADOQuery SQL属性,作为对 SQL.Text 的单个直接更改属性:

sql := TStringList.Create;
try
  sql.Add(..);
  sql.Add(..);
  sql.Add(..);
  sql.Add(..);
  sql.Add(..);
  sql.Add(..);

  qryInsert.SQL.Text := sql.Text;

finally
  sql.Free;
end;

无论哪种方式,结果都是VCL将解析参数,内部ADO对象将仅一次更新,并带有完整且(希望)语法正确的SQL语句。

第二种方法可能涉及少一点“样板” - 尝试 .. 最后这里纯粹是为了管理临时字符串列表。如果为了这个目的而在更广泛的范围内重用对象,或者使用产生简单字符串的SQL构建器助手类(就像我一样),则不需要这个特定的尝试 .. 终于,使这更方便,更清晰:

SQLBuilder.Insert('MyTable');
SQLBuilder.AddColumn('ColumnOne');
SQLBuilder.AddColumn('ColumnTwo');

qryInsert.SQL.Text := SQLBuilder.SQL;

// qryInsert.SQL == INSERT INTO MyTable (ColumnOne, ColumnTwo)
//                  VALUES (:ColumnOne, :ColumnTwo)    

例如。

String vs TStringList

如果您构建SQL的首选技术产生的是字符串列表而不是简单的字符串,那么您可能想直接分配字符串列表:

  qryInsert.SQL := sql;

但请注意,这会执行 sql stringlist的 Assign(),从而有效地执行“深层复制”。您仍然需要确保已正确释放分配的字符串列表(上面代码中的 sql )。

另请注意,这也是效率较低的,因为它还复制了stringlist的其他属性,包括与列表中每个字符串关联的所有对象。在这种情况下,您只想复制字符串列表的文本内容,不需要产生(轻微)和不必要的开销。

答案 1 :(得分:2)

  

只有AV(并且始终)在调试时出现,如果我通过“.exe”直接执行应用程序,它永远不会出现。

     

...

     

在屏幕上出现所有AV后,插入工作正常。我只是想明白为什么一切看起来都没有这个错误。

在外部模块中引发访问冲突,使用Delphi以外的语言实现。很可能外部代码的行为正确且符合设计,并且预计会发生访问冲突。

这可能听起来很奇怪,但外部代码清楚地处理异常,因为控件没有传递给代码的异常处理程序。如您所见,该程序正常工作。

这就是所谓的first chance exception。调试器会收到通知并中断。但是然后控制返回到程序,在这种情况下,程序处理异常并继续。对于代码来说,引发第一次机会访问冲突异常,但仍能正常运行,这是完全正常的,尽管可能是反直觉的。作为该声明的证据,请参阅VS开发团队成员撰写的article

  

为什么VS调试器在第一次机会访问冲突时没有停止(默认情况下)?

     

...

     

第一次机会AV的默认值不会停止的原因是   有时Windows调用会AV,然后捕获异常   他们自己,幸福地继续前进。如果我们默认停止   第一次机会AV我们会在一些陌生的地方阻止用户说   kernel32.dll和许多人会非常困惑。

因此,就正确性而言,我认为没有什么可担心的。但它确实使调试变得困难。试试@Deltics提出的各种建议。如果通过进行这些更改,您可以避免异常,那就是好事。否则,您可能需要(至少暂时)禁止调试器中断异常。

答案 2 :(得分:0)

如果qrySelect没有'ColumnTwelve'那么

if qrySelect.FieldByName('ColumnTwelve').AsSTring = 'Y' then

将引发异常,因为FieldByName将返回nil