4

我有这个代码返回访问冲突('模块'sqloledb.dll'中地址74417E44的访问冲突。读取地址786E3552'),我无法确定问题出在哪里。我唯一的猜测是 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,                                    ');

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

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

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

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


笔记:

  • 我正在使用 Delphi 7 和 Windows 8.1。

  • AV 仅(并且总是)在调试时出现,如果我直接通过其“.exe”执行应用程序,它永远不会出现。

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

  • 在所有 AV 出现在屏幕上后,插入工作。我只是想了解当一切看起来都很好时为什么会出现这个错误。

4

3 回答 3

6

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

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

在这种情况下,当您添加第 11 个参数时,该 SQL 已被应用和解析 28 次!

然后产生的 AV 发生在SQLOLEDB.DLL中的事实表明,无论发生什么问题都是由于对应用于内部 ADO 对象的 SQL 的更改而不是在 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 语句。

第二种方法可能涉及更少的“样板”——try .. finally这里纯粹是为了管理临时字符串列表。如果您为此目的在更广泛的范围内重新使用一个对象,或者使用产生一个简单字符串的 SQL 构建器帮助程序类(就像我一样),那么就不需要这个特定的try .. finally,让它多一点使用方便、清洁:

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

qryInsert.SQL.Text := SQLBuilder.SQL;

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

例如。

字符串与 TStringList

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

  qryInsert.SQL := sql;

但请注意,这会执行sql字符串列表的Assign(),有效地执行“深度复制”。您仍然需要确保分配的字符串列表(上述代码中的sql )被适当地释放。

另请注意,这也不太有效,因为它还复制了字符串列表的其他属性,包括与列表中每个字符串关联的任何对象。在这种情况下,您只对复制字符串列表的Text内容感兴趣,因此无需产生(轻微的)和不必要的开销。

于 2015-05-19T00:20:15.837 回答
2

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

……

在所有 AV 出现在屏幕上后,插入工作。我只是想了解当一切看起来都很好时为什么会出现这个错误。

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

这听起来可能很奇怪,但外部代码清楚地处理了异常,因为控制不会传递给代码的异常处理程序。正如您所观察到的,该程序运行正常。

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

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

……

first-chance AVs 默认不停止的原因是有时 Windows 调用会 AV,然后自己捕获异常并愉快地继续。如果我们确实默认停止第一次机会 AV,我们会在 kernel32.dll 中某个奇怪的地方停止用户,许多人会非常困惑。

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

于 2015-05-19T07:19:03.750 回答
0

如果 qrySelect 没有 'ColumnTwelve' 则

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

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

于 2015-05-18T14:26:49.107 回答