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

Joã*_*ira 4 delphi access-violation tadoquery

我有这个代码返回访问冲突('模块'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;
Run Code Online (Sandbox Code Playgroud)

我在这一行得到错误:

Sql.Add('       :ColumnTwelve,                                    ');
Run Code Online (Sandbox Code Playgroud)

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

Sql.Add('     ' + varColumnTwelve + ',                            ');
Run Code Online (Sandbox Code Playgroud)

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

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


笔记:

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

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

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

  • 插件在屏幕上出现所有AV后工作.我只是想明白为什么一切看起来都很好我会收到这个错误.

Del*_*ics 6

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

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

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

然后,结果发生在SQLOLEDB.DLL中的事实表明,无论发生什么问题,都会将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;
Run Code Online (Sandbox Code Playgroud)

注意: 将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;
Run Code Online (Sandbox Code Playgroud)

这样可以抑制ADO查询对象内部的OnChange事件,直到调用EndUpdate,此时SQL将应用于内部ADO对象并更新查询对象的Parameters.

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

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;
Run Code Online (Sandbox Code Playgroud)

无论哪种方式,结果将是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)    
Run Code Online (Sandbox Code Playgroud)

例如.

字符串与TStringList

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

  qryInsert.SQL := sql;
Run Code Online (Sandbox Code Playgroud)

但要注意,这个执行分配()中的SQL的StringList,有效地执行"深拷贝".您仍然需要确保已正确释放分配的字符串列表(上面代码中的sql).

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