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,                                    ');

これは、insert ステートメントの 11 番目のパラメーターです。この行にコメントすると、次のパラメーターでエラーが発生します。次のように値を直接入力すると:

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

正常に動作しますが、次のパラメーターでエラーが発生します。

ADOQuery には、処理できるパラメーターの数に制限があるのでしょうか。または、これが本当の問題ではない場合、これを修正する方法の手がかりを誰かが持っていますか?


ノート:

  • Delphi 7 と Windows 8.1 を使用しています。

  • AVはデバッグ時にのみ(そして常に)表示されます。「.exe」を介してアプリケーションを直接実行すると、AVは表示されません。

  • エラーが表示された後も「実行」を押し続けると、アプリケーションが正常に動作し続けるまで、AV がどんどん表示されます (AV の数は、10 番目以降に追加されたパラメーターの数と同じだと思います)。

  • 挿入、すべての AV が画面に表示された後に機能します。すべてが正常に見えるのに、なぜこのエラーが発生するのかを理解したいだけです。

4

3 に答える 3

6

TADOQueryの SQL プロパティを変更すると、TADOQueryはその変更に応答し、変更された SQL を内部 ADO コンポーネント オブジェクトに再適用し、SQL を再解析してパラメータを識別します。

このため、この方法で SQL を段階的に変更することはお勧めできません。何よりも、SQL を完全にアセンブルする前に、SQL を何度も適用して解析するのは非常に非効率的です。

この場合、11 番目のパラメーターを追加する時点までに、SQL は 28 回適用され、解析されています。

その結果、AV がSQLOLEDB.DLL内で発生しているという事実は、発生している問題が、パラメーターなどを識別するための VCL 処理ではなく、内部 ADO オブジェクトに適用されている SQL への変更の結果であることを示唆しています。問題を解決するためにできることはあまりありません。あなたができる最善のことは、それを避けることです。

SQL を変更する際にParamCheck := FALSEを設定すると、この処理の一部を削除できます。これにより、VCL が変更された SQL を再解析してパラメーターを識別しようとするのを防ぎます。ただし、各変更に応じて、基になる ADO コンポーネントに SQL が再適用されるのを防ぐことはできません。

診断の練習として、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 が引き続き発生する場合は、おそらく不完全な (構文的に正しくない) SQL を適切に処理できなかったことが原因で、SQL への繰り返しの変更に対応して適切に動作しない内部 ADO コンポーネントの問題をさらに強く示しています。

ただし、 2 つの方法のいずれかによって、変更メカニズムを完全にトリガーすることを回避できます。

おそらく最も簡単なのは、SQL を構築するコードの周囲でTADOQuery SQL文字列リストに対してBeginUpdate / EndUpdateを使用することです。

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

finally
  qryInsert.SQL.EndUpdate;
end;

これには、EndUpdateへの呼び出しまで、ADO クエリ オブジェクト内の内部OnChangeイベントを抑制する効果があります。この時点で、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 オブジェクトは1 回だけ更新され、完全で (願わくば) 構文的に正しい SQL ステートメントになります。

この 2 番目のアプローチでは、"ボイラープレート" を少し減らすことができますここでは、一時的な文字列リストを純粋に管理します。この目的のためにより広い範囲でオブジェクトを再利用していた場合、または単純な文字列を生成する 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 ) が適切に解放されていることを確認する必要があります。

リスト内の各文字列に関連付けられたオブジェクトを含む、文字列リストの他のプロパティもコピーするため、これも効率が悪いことに注意してください。この場合、stringlist のTextコンテンツ全体のコピーのみに関心がある場合、(わずかな) 不必要なオーバーヘッドが発生する必要はありません。

于 2015-05-19T00:20:15.837 に答える