我有这个代码返回访问冲突('访问冲突,地址为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后,插入工作。我只是想明白为什么一切看起来都没有这个错误。
答案 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)
例如。
如果您构建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