Migaro. 技術Tips

                       

ミガロ. 製品の技術情報
IBMiの活用に役立つ情報を掲載!


【Delphi/400】FireDAC接続でApplyUpdatesを行う手順

前回のTipsで、各接続方式で
TClientDataSetからApplyUpdatesを行う手順について紹介しました。

TClientDataSetコンポーネント使用時は
『各種Query(Table) ⇒ TDataSetProvider ⇒ TClientDataSet
というコンポーネントの配置になっており、TDataSetProviderが中継役を担っています。
ApplyUpdatesメソッドを通ると内部で更新SQLが生成・発行されて、変更がDBに反映されます。

一方でFireDAC接続のTFDMemTableコンポーネント使用時は、
『TFDQuery(TFDTable) ⇒ TFDMemTable
というコンポーネントの配置になっており、中継するコンポーネントがありません。
そのためデータの参照時と同様に、変更の反映時にも同様に個別にデータを転送する必要があります。

今回はその手順を紹介します。
(TClientDataSetを使用する手順については、前回のTipsをご参照ください。)

 

1. 事前準備

前回のTipsで紹介したTClientDataSetでのApplyUpdatesと同様に、
複数の更新を一括で行うという機能上、トランザクション処理が推奨になります。
(必須ではありませんが、処理の途中でエラーになった際にロールバックできると復旧が容易です)

トランザクション処理を行う場合は、次の2点の準備が必要となります。

  1. IBM i 上で対象ファイルのジャーナルが開始されていること
    (他DBの場合もファイル側のトランザクションが有効になっていること)
  2. トランザクションレベルで *NONE 以外の値が設定されていること

こちらの詳細については、それぞれ前回のTipsをご参照ください。

 

2. OnUpdateRecordイベントの割り当て

TFDMemTableコンポーネントにもApplyUpdatesメソッドは存在しますが、
上記の通り中継役のコンポーネントが無いため、
ApplyUpdatesメソッドを呼び出すだけでは内部で更新処理は行われません。

更新処理を行うには、TFDMemTableの「OnUpdateRecord」イベントを定義し、
その中でINSERT・UPDATE・DELETEそれぞれの処理を記述します。

 

3. 更新ロジックの記述

OnUpdateRecordイベントは、
TFDMemTableのApplyUpdatesメソッドの内部で更新1件単位で呼び出されます。
この中で更新処理を1レコード単位で記述することができます。

ここではボタン押下時にApplyUpdatesメソッドを行い、
その中で「qryApply」という更新用の別のTFDQueryを使用し、
レコードの挿入/変更/削除それぞれのSQL発行を行う例を記述します。

※今回の例で記載しているファイル「TESTFILE」は、
 キーフィールド「W1KEY1」「W1KEY2」でユニークになっている設計です。
 実際のSQL記述時は、実際のフィールド設計にあわせて下さい。

※今回の場合も、内部的には更新SQLを発行する都合上、
 参照元のファイルがUNIQUEでなく、かつ全てのフィールドの値が全く同じレコードが複数ある場合、
 それらの全てのレコードが更新(UPDATE/DELETE)されてしまいます。
 (TClientDataSetの場合と異なりエラーにはなりません)

 
<ApplyUpdatesを行うボタン押下時処理>

{*******************************************************************************
 目的 : ApplyUpdatesボタン押下時処理
 引数 :
 戻値 :
*******************************************************************************}
procedure TForm1.btnApplyClick(Sender: TObject);
var
  iErrors: Integer;
begin
  if not FDMemTable1.Active then
  begin
    ShowMessage('FDMemTable1を開いてから適用してください。');
    Exit;
  end;

  if FDMemTable1.State in dsEditModes then
    FDMemTable1.Post;

  if not FDMemTable1.UpdatesPending then
  begin
    ShowMessage('適用する変更がありません。');
    Exit;
  end;

  FDConnection1.StartTransaction; // トランザクション開始
  try
    iErrors := FDMemTable1.ApplyUpdates(-1); // 変更を反映
    if iErrors = 0 then
    begin
      FDConnection1.Commit;   // コミット
      ShowMessage('FDMemTable1の変更をDBに適用しました。');
    end
    else
    begin
      FDConnection1.Rollback; // ロールバック
      ShowMessage(IntToStr(iErrors) + '件の変更を適用できませんでした。');
    end;
  except
    on E: Exception do
    begin
      if FDConnection1.InTransaction then
      begin
        FDConnection1.Rollback; // ロールバック
        raise; // 元々の例外を再生成
      end;
    end;
  end;
end;

 
<FDMemTable OnUpdateRecordイベント>

{*******************************************************************************
 目的 : FDMemTable OnUpdateRecordイベント(ApplyUpdatesの途中で呼び出される)
 引数 : ASender  - 更新を適用するFDMemTable(今回はFDMemTable1)
        ARequest - 今回の更新がレコードの挿入、変更、削除のどれか?
        AAction  - (戻)イベントが終了時に実行されるアクション
        AOptions - 今回は不使用
 戻値 :
*******************************************************************************}
procedure TForm1.FDMemTable1UpdateRecord(ASender: TDataSet;
  ARequest: TFDUpdateRequest; var AAction: TFDErrorAction;
  AOptions: TFDUpdateRowOptions);
begin

  case ARequest of
    arInsert: // レコード挿入の場合
      begin
        qryApply.SQL.Text :=
          'INSERT INTO TESTFILE ' +
          '(W1KEY1, W1KEY2, W1CHNP, W1CHAS, W1CHWS) VALUES ' +
          '(:W1KEY1, :W1KEY2, :W1CHNP, :W1CHAS, :W1CHWS)';
        qryApply.ParamByName('W1KEY1').AsString  := ASender.FieldByName('W1KEY1').AsString;
        qryApply.ParamByName('W1KEY2').AsInteger := ASender.FieldByName('W1KEY2').AsInteger;
        qryApply.ParamByName('W1CHNP').AsString  := ASender.FieldByName('W1CHNP').AsString;
        qryApply.ParamByName('W1CHAS').AsString  := ASender.FieldByName('W1CHAS').AsString;
        qryApply.ParamByName('W1CHWS').AsString  := ASender.FieldByName('W1CHWS').AsString;
      end;

    arUpdate: // レコード変更の場合
      begin
        qryApply.SQL.Text :=
          'UPDATE TESTFILE SET ' +
          'W1KEY1 = :W1KEY1, W1KEY2 = :W1KEY2, W1CHNP = :W1CHNP, ' +
          'W1CHAS = :W1CHAS, W1CHWS = :W1CHWS ' +
          'WHERE W1KEY1 = :OLD_W1KEY1 AND W1KEY2 = :OLD_W1KEY2 ';
        qryApply.ParamByName('W1KEY1').AsString  := ASender.FieldByName('W1KEY1').AsString;
        qryApply.ParamByName('W1KEY2').AsInteger := ASender.FieldByName('W1KEY2').AsInteger;
        qryApply.ParamByName('W1CHNP').AsString  := ASender.FieldByName('W1CHNP').AsString;
        qryApply.ParamByName('W1CHAS').AsString  := ASender.FieldByName('W1CHAS').AsString;
        qryApply.ParamByName('W1CHWS').AsString  := ASender.FieldByName('W1CHWS').AsString;
        qryApply.ParamByName('OLD_W1KEY1').AsString  := ASender.FieldByName('W1KEY1').OldValue;
        qryApply.ParamByName('OLD_W1KEY2').AsInteger := ASender.FieldByName('W1KEY2').OldValue;
      end;

    arDelete: // レコード削除の場合
      begin
        qryApply.SQL.Text :=
          'DELETE FROM TESTFILE ' +
          'WHERE W1KEY1 = :OLD_W1KEY1 AND W1KEY2 = :OLD_W1KEY2';
        qryApply.ParamByName('OLD_W1KEY1').AsString  := ASender.FieldByName('W1KEY1').OldValue;
        qryApply.ParamByName('OLD_W1KEY2').AsInteger := ASender.FieldByName('W1KEY2').OldValue;
      end;
  else
    Exit;
  end;

  qryApply.ExecSQL;

  // 1行も更新されなかった場合のエラー例
  if (ARequest in [arUpdate, arDelete]) and (qryApply.RowsAffected = 0) then
    raise Exception.Create('更新対象レコードが見つかりませんでした。');

  // 処理成功を呼出元に返す
  AAction := eaApplied;
end;

 

<関連リンク>※Embarcadero Docwiki