前回のTipsで、各接続方式で
TClientDataSetからApplyUpdatesを行う手順について紹介しました。
TClientDataSetコンポーネント使用時は
『各種Query(Table) ⇒ TDataSetProvider ⇒ TClientDataSet』
というコンポーネントの配置になっており、TDataSetProviderが中継役を担っています。
ApplyUpdatesメソッドを通ると内部で更新SQLが生成・発行されて、変更がDBに反映されます。
一方でFireDAC接続のTFDMemTableコンポーネント使用時は、
『TFDQuery(TFDTable) ⇒ TFDMemTable』
というコンポーネントの配置になっており、中継するコンポーネントがありません。
そのためデータの参照時と同様に、変更の反映時にも同様に個別にデータを転送する必要があります。
今回はその手順を紹介します。
(TClientDataSetを使用する手順については、前回のTipsをご参照ください。)
1. 事前準備
前回のTipsで紹介したTClientDataSetでのApplyUpdatesと同様に、
複数の更新を一括で行うという機能上、トランザクション処理が推奨になります。
(必須ではありませんが、処理の途中でエラーになった際にロールバックできると復旧が容易です)
トランザクション処理を行う場合は、次の2点の準備が必要となります。
- IBM i 上で対象ファイルのジャーナルが開始されていること
(他DBの場合もファイル側のトランザクションが有効になっていること) - トランザクションレベルで *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
- FireDAC.Comp.DataSet.TFDDataSet.ApplyUpdates – RAD Studio API Documentation
- FireDAC.Comp.DataSet.TFDDataSet.OnUpdateRecord – RAD Studio API Documentation