Table of Contents
データを更新するアプリケーション作成の演習
本ブログでは、Delphi / C++Builderをある程度使用したことのある方をを対象として、FireDACの基本的な利用方法を解説していきます。
第3回のテーマ
- FireDACのデータセット概要
- データセットを利用する具体例
- データを更新するアプリケーション作成の演習
前回、実際にFireDACを使用した簡単なアプリケーションを作成しました。 データを表示する際には、データセットを利用していました。データベースにクエリーを発行し、その結果セットを保持するという基本的なデータアクセスの機能を実現するのに、このデータセットを使うのですが、その詳細には触れていませんでした。そこで今回のブログでは、そのFireDACのデータセットの詳細について解説していきたいと思います。
データセットは、データアクセスを行うアプリケーションを開発する上で、特に抑えておきたい最重要な項目の一つです。
FireDACのデータセット概要
データセットとは、端的に言うとデータベースから取得したレコード(データ)をメモリ内に保持するための「入れ物」という概念です
Delphi / C++Builderのデータセットは、データベースから取得した単一あるいは複数のテーブルのレコードを(Delphi / C++Builderで)扱いやすいデータ形式にマッピングし保持します。そしてそのデータは、行と列で全て表すことができます。
しかし、データセットは単なる入れ物ではなく、レコード(データ)の整理やデータの操作など、複雑な動作を伴う処理を、カプセル化したデータセットオブジェクト(クラス)として提供しています。
Delphi / C++Builderのデータセットオブジェクト(以下、データセット)には、基本的なプロパティ、イベント、およびメソッドをもつ「TDataSet」という名前のクラスがあり、これは全てのデータセットの基底クラスになります。
Delphi / C++Builderで作成するデータベースへアクセスするアプリケーションは、全てTDataSet から派生するデータセットを使用します。これにより、どのようなデータセットを使用したとしても、TDataSetによって決められた方式を用いることができ、データセットによるデータアクセスをとても簡素化できるのです。
FireDACのデータセットもTDataSetクラスから派生しており、FireDAC独自の機能を持ったデータセットへ拡張されています。
FireDACのデータセットの継承図は、以下の通りです。
FireDACでよく利用する主なデータセットには、以下のような種類があります。
データセット名 | 用途 | 特徴 |
---|---|---|
TFDTable | 単一のテーブルを扱うデータセット | BDEのTTable相当 |
TFDQuery | SQL文の実行および結果セット(複数可)を扱うデータセット | 実行パフォーマンスは、 TFDTableを利用するよりも優秀 |
TFDMemTable | インメモリのデータセット | TClientDataSetと同様にローカルDBとして利用可能 |
※TFDMemTableの詳細は、本ブログシリーズの別の回で紹介いたします。
FireDACのデータセットの全般の特徴は、BDEのデータセットと互換性が高くコードレベルや機能面で移行しやすい点が挙げられます。
ただ、予めご留意いただきたいのは、BDEのTTableからの移行に関する注意点です。
FireDACでは、BDEからの移行のためにTTableと互換性を持つTFDTableを用意していますが、TFDTableの利用に関しては制限も多く、特にデータの取得の際に余計なオーバーヘッドが生じることがあり、実行パフォーマンス面で問題が発生することがあります。詳しくは、こちらを参照ください。
TTableからの置き換えるデータセットの候補として、TFDQueryを推奨しています。 TFDQueryは、BDEのTTableと共通するプロパティやメソッドを持っており、実行パフォーマンスもTFDTableより優れているので、置き換える候補として最適です。 |
※FireDACの実行パフォーマンス向上のヒントは、本ブログシリーズの別の回で紹介いたします。
それでは、FireDACのデータセットの具体的な使用例について見ていきましょう。
データセットを利用する具体例
次節でアプリケーションを作成する演習を行いますが、その前にTFDQueryのデータセットを例として、基本的な使い方をいくつか説明します。
もう既にデータセットの使い方を知っている方は、このセクションをスキップして演習を行いましょう。
データの取得と参照
データセットのOpenメソッドを実行すると、データ(結果セット)取得できます。
1 |
FDQuery1.Open('select * from Employee'); |
1 2 |
FDQuery1.SQL.Text:= 'select * from Employee'; FDQuery1.Open(); |
1 |
FDQuery1->Open("select * from Employee"); |
1 2 |
FDQuery1->SQL->Text = "select * from Employee"; FDQuery1->Open(); |
TFDQueryにはSQLプロパティがあります。オープンする前にSQL文を指定しておくと、Openメソッドをパラメータなしで実行することができます。
またパラメータ付きのクエリを実行することもできます。 以下はその例です。
1 2 3 |
FDQuery1.SQL.Text:='select * from Employee where EMP_NO = :empno'; FDQuery1.ParamByName('empno').AsInteger:=11; FDQuery1.Open(); |
1 2 3 |
FDQuery1->SQL->Text="select * from Employee where EMP_NO = :empno"; FDQuery1->ParamByName("empno")->AsInteger=11; FDQuery1->Open(); |
パラメータを SQL テキストに含めるには、:<名前>構文を使用します。そしてTFDQueryのParamByName(<名前>)メソッドに値を代入することでパラメータを指定することができます。
<名前>には、必ず同じ(名前)文字列を指定してください。
上記のコードの例では、<名前>=empno です。
データセットを開いて取得した結果セットは、TFDQueryのFieldByName( <フィールド名>)メソッドなどで参照できます。
以下はその例です。
1 2 3 4 5 6 7 |
FDQuery1.Open('select FIRST_NAME from Employee'); while not FDQuery1.Eof do begin //参照したデータをメモへ出力 Memo1.Lines.Add(FDQuery1.FieldByName('FIRST_NAME').AsString); FDQuery1.Next; end; |
1 2 3 4 5 6 |
FDQuery1->Open("select FIRST_NAME from EMPLOYEE"); while (!FDQuery1->Eof) { //参照したデータをメモへ出力 Memo1->Lines->Add(FDQuery1->FieldByName("FIRST_NAME")->AsString); FDQuery1->Next(); } |
データセットを閉じる
データセットを閉じるには、Closeメソッドを呼び出します。
以下は、データセットを再オープンするコード例です。
1 2 |
FDQuery1.Close; FDQuery1.Open('select * from Employee'); |
1 2 |
FDQuery1->Close(); FDQuery1->Open("select * from Employee"); |
データの編集/更新
Editメソッドを呼び出すとデータセットの現在のカーソル行のデータを編集でき、Postメソッドを呼び出すと編集したデータを更新することができます。
データを更新するコード例は以下の通りです。
1 2 3 4 5 6 7 |
FDQuery1.SQL.Text:='select * from EMPLOYEE Where EMP_NO=:empno'; FDQuery1.ParamByName('empno').AsInteger:=11; FDQuery1.Open; FDQuery1.Edit; FDQuery1.FieldByName('FIRST_NAME').AsString:='Marco'; FDQuery1.FieldByName('LAST_NAME').AsString:='Cantu'; FDQuery1.Post; |
1 2 3 4 5 6 7 |
FDQuery1->SQL->Text="select * from EMPLOYEE Where EMP_NO=:empno"; FDQuery1->ParamByName(“empno”)->AsInteger=11; FDQuery1->Open(); FDQuery1->Edit(); FDQuery1->FieldByName("FIRST_NAME")->AsString="Marco"; FDQuery1->FieldByName("LAST_NAME")->AsString="Cantu"; FDQuery1->Post(); |
FireDACのデータセットでデータの更新を行ったとき、以下のエラーが発生することがあります。
[FireDAC][DApt]-400. Update コマンドが updated したのは [x] レコードで [1] レコードではありません。考え得る理由: 更新テーブルに主キーまたは行識別子がありません。レコードは別のユーザーによって変更または削除されました |
FireDACでは、主キーが設定されていないテーブルの場合、更新対象のレコードが複数[x]行検出されることがあるため、上記のようなエラーが発生します。
特にBDEからFireDACへ移行すると、遭遇しやすい事象のため注意してください。
もしこのエラーに遭遇した場合は、以下の設定を試してください。(docwikiから抜粋)
FetchOptions.Items から fiMeta を除き、以下のいずれかを使用します。
・UpdateOptions.KeyFields を列名のセミコロン(’;’)区切りリストに設定します。
・pfInKey を対応する TField.ProviderFlags プロパティに含めます。
詳しくは、こちらの注意事項を参照してください。
|
SQL文の実行
TFDQueryは、直接SQL文を実行して、レコードの追加、更新、削除などが行えます。
以下は、レコードの更新(update文)を実行するコード例です。
1 2 3 |
FDQuery1.SQL.Text :='update EMPLOYEE set FIRST_NAME=:NAME where EMP_NO=15'; FDQuery1.ParamByName('NAME').AsString:='Marco'; FDQuery1.ExecSQL; |
1 2 3 |
FDQuery1->SQL->Text :="update EMPLOYEE set FIRST_NAME=:NAME where EMP_NO=15"; FDQuery1->ParamByName("NAME")->AsString="Marco"; FDQuery1->ExecSQL; |
結果セットの取得を必要としない insert、update、deleteなどのSQL文を実行する場合は、Openメソッドではなく、ExecSQLメソッドを実行します。
またFireDACでは、フォーム上に配置されているTFDQueryを選択し、マウスを右クリックして[クエリエディタ]を起動できます。
FireDACのクエリエディタでは、[SQLコマンド]にSQL文を入力して、その場で実行することができます。
SQLコマンドに入力されたSQL文は、[OK]ボタンを押すと、そのままTFDQuery.SQLプロパティに保存されます。
トランザクション処理の実装
FireDACでは、トランザクションを管理する方法は2種類あります。
- TFDConnectionのStartTransaction、Commit、Rollback の各メソッドを使用する
- TFDTransaction コンポーネントの使用し、 TxOptionsオプションを設定する
本節では、TFDConnectionによるトランザクション管理のコード例を示します。
1 2 3 4 5 6 7 8 9 10 |
FDConnection1.StartTransaction; // トランザクションの開始 try FDQuery1.SQL.Text :='update EMPLOYEE set FIRST_NAME=:NAME where EMP_NO=15'; FDQuery1.ParamByName('NAME').AsString:='Marco'; FDQuery1.ExecSQL; FDConnection1.Commit; //SQL文が正常に実行された場合は、コミット except FDConnection1.Rollback; //例外が発生した場合は、ロールバック .. end; |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
FDConnection1->StartTransaction(); // トランザクションの開始 try { FDQuery1->SQL->Text :="update EMPLOYEE set FIRST_NAME=:NAME where EMP_NO=15"; FDQuery1->ParamByName("NAME")->AsString="Marco"; FDQuery1->ExecSQL; FDConnection1->Commit(); //SQL文が正常に実行された場合は、コミット } catch(Exception &e) { FDConnection1->Rollback(); //例外が発生した場合は、ロールバック .. } |
キャッシュアップデート
キャッシュアップデートとは、簡単に説明すると、データを更新するたびにデータベースに対してポストするのではなく、データセットの更新情報をすべてローカルキャッシュに保存しておいて、「後でまとめてポストする」機能のことです。BDEではキャッシュアップデートを利用できましたが、FireDACでもこの機能は 引き続き利用することができます。
キャッシュアップモードを有効にするには、各データセットで以下の設定が必要です。
- CachedUpdatesプロパティを True に設定
- ApplyUpdates メソッドを実行
キャッシュアップデートについては、次のセクションで具体的な実装コードを交えて解説しましょう。
データを更新するアプリケーション作成の演習
このセクションでは、第2回で行ったFireDACによるアプリケーション作成の改良版として、今回、データの取得とデータ更新(トランザクション)処理を行う実装コードを追加し、より実際のアプリケーション開発に近づけた演習を行います。
演習手順は以下の通りです。
- データベースへの接続のセットアップと、その接続の確立
- 実行に必要なFireDACのコンポーネントの配置
- データセットをグリッドコントロールと結び付ける
- データの取得とデータ更新(トランザクション)処理を実装
- アプリケーションを実行し、データの表示と更新
今回のブログでも、接続するデータベースとしてInterBase 2020を使用します。
(1)VCLフォームアプリケーションのプロジェクト作成
Delphi/C++Builderのメニューから[ファイル]-[新規作成]-[Windows VCLフォームアプリケーション]を選択します。
(2)プロジェクトを保存する
メニューの[ファイル]-[すべて保存] を選択し、全てのファイルを保存してください。プロジェクトは、任意のフォルダに保存することができます。
(3)InterBaseサーバーを起動する
Windowsスタートメニューの”Embarcadero InterBase 2020 [instance=gds_db]”-”InterBase サーバーマネージャー”を選択し、サーバーマネージャーを起動してください。
InterBase サーバーマネージャー画面の[起動]ボタンを押してInterBaseのプロセスを開始します。
サーバーの状態が”動作中”であることを確認してください。
(4)フォーム上にFireDACのコンポーネントを配置する
FireDACからデータベースへ接続するためには、最低1個以上、TFDConnectionコンポーネントの配置が必要です。 |
をそれぞれフォーム上の任意の位置へ配置します。
TFDPhysXXXDriverLinkは、接続するデータベースごとに必要です。 例えば、InterBaseへ接続する場合は、TFDPhysIBDriverLinkを使用します(Delphi/C++Builder XE6以降は、TFDPhysXXXDriverLinkの配置は必須ではなく、任意)。
但し、独自のライブラリファイルを指定する必要がある場合は、VenderHome、VenderLibプロパティの設定を明示的に行う必要があるため、今まで通り、TFDPhysXXXXDriverLinkの配置が必要です。 |
このブログでは、明示的にTFDPhysIBDriverLinkを配置することにします。
(5)フォーム上にその他のコンポーネントを配置する
フォーム上の任意の位置へ配置します(下図のは配置例を参照)。
(6)FireDAC接続エディタの表示
フォーム上のFDConnection1を選択し、マウスを右クリックするとポップアップメニューが表示されるので、そのメニューから[接続エディタ]を選択すると、FireDAC 接続エディタが表示されます。
(7)TFDConnectionの接続パラメータの設定
FireDAC接続エディタのドライバIDのリストから”IB”を選択します。
ドライバIDを選択すると、データベースの設定に必要なパラメータリストが表示されます。
FireDAC接続エディタ(上図)の空欄箇所を以下のパラメータのように変更してください。
パラメータ名 | 値 |
ドライバID | IB |
Database | C:ProgramDataEmbarcaderoInterBasegds_dbexamplesdatabaseemployee.gdb |
User_Name | SYSDBA |
Password | masterkey |
(8)データベースへの接続をテストする
データベースへ接続に必要なパラメータを設定後、FireDAC接続エディタの[テスト]ボタンを押してください。
テストボタンを押すと、データベースへのログイン画面が表示されます。
[OK]ボタンを押してください。
“接続の確立が成功しました。”
というダイアログメッセージが表示されれば、データベースへの接続は、正常に完了です。
データベースへの接続が確認できたら、フォーム上のFDConnection1を選択し、オブジェクトインスペクタの画面から以下のプロパティを変更します。
プロパティ名 | 値 |
LoginPrompt | False |
FDConnection1.LoginPromptプロパティをTrueの場合、アプリケーション起動時に毎回、データベースへの接続画面が表示されますので、ここではFalseに変更します。
(9)それぞれのプロパティを変更する
オブジェクトインスペクタの画面から各コンポーネントの以下のプロパティを変更します。
プロパティ名 | 値 |
Connection | FDConnection1 |
CachedUpdates | True |
FireDACでは、ApplyUpdateメソッドを利用してデータセットを更新するためにCachedUpdateプロパティをTrueに設定しておく必要があります。
FDQuery1のCachedUpdateプロパティをTrueに設定せず、ApplyUpdateを実行すると上図のような例外が発生します。 |
プロパティ名 | 値 |
DataSet | FDQuery1 |
プロパティ名 | 値 |
DataSource | DataSource1 |
プロパティ名 | 値 |
Caption | データセットを開く |
プロパティ名 | 値 |
Caption | ApplyUpdateを実行 |
(10)データセットを開く処理を実装する
設計画面でButton1をダブルクリックすると、Button1のOnClickのイベントハンドラが生成されます。そのイベントハンドラ内にデータセットを開く処理を追加します。
1 2 3 4 5 |
procedure TForm1.Button1Click(Sender: TObject); begin FDQuery1.Close; FDQuery1.Open('select * from Employee'); end; |
1 2 3 4 5 |
void __fastcall TForm1::Button1Click(TObject *Sender) { FDQuery1->Close(); FDQuery1->Open("select * from Employee"); } |
FireDACでは、Openメソッドの引数としてSelect文を指定することができます。但し、結果セットを取得しないinsert/update/delete文は指定できません。 |
(11)ApplyUpdateを実行する処理を実装する
設計画面でButton2をダブルクリックすると、Button2のOnClickのイベントハンドラが生成されます。そのイベントハンドラ内にApplyUpdateを実行する処理を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
procedure TForm1.Button2Click(Sender: TObject); var ErrorCount: Integer; begin FDConnection1.TxOptions.AutoCommit:=False; //自動コミットモードOFF FDConnection1.StartTransaction; //明示的なトランザション開始 ErrorCount := FDQuery1.ApplyUpdates(-1); if ErrorCount = 0 then FDConnection1.Commit //エラーが無ければコミット else FDConnection1.Rollback; //エラーがあれば、ロールバック end; |
1 2 3 4 5 6 7 8 9 10 11 12 |
void __fastcall TForm1::Button2Click(TObject *Sender) { FDConnection1->TxOptions->AutoCommit=False; //自動コミットモードOFF FDConnection1->StartTransaction(); //明示的なトランザション開始 int ErrorCount=FDQuery1->ApplyUpdates(-1); if( ErrorCount==0 ){ FDConnection1->Commit(); //エラーが無ければコミット }else{ FDConnection1->Rollback(); //エラーがあればロールバック } } |
ApplyUpdatesメソッドは、例外を発生しない代わりに、発生した例外エラーの数を返します。
そのため、try~catchで例外を補足するのではなく、例外エラーの数で判断し、コミット/ロールバックどちらを実行するか決めましょう。
|
TFDConnectionのStartTransactionメソッドは、明示的なトランザクションを開始する際に呼び出します。
但し、TFDTxOptions.AutoCommitプロパティがTrueになっていると、自動コミットモードが有効になるので、このプロパティはFalseに設定しましょう。
|
(12)プロジェクトを保存する
メニューの[ファイル]-[すべて保存] を選択し、全てのファイルを保存してください。
(13)アプリケーションを実行する
ツールバー(上図)の実行ボタン、または、キーボードの[F9]ボタンを押します。
(14)データセットを開く
アプリケーションを実行した後、[データセットを開く]ボタンを押すと、DBGridコンポーネントにEmployeeテーブルのデータが表示されます。
(15)グリッド内の任意のデータを変更する
DBGridに表示されている任意のデータを変更します。
例えば、
FIRST_NAMEフィールドの”K.J.”を”Marco”
LAST_NAMEフィールドの”Weston”を”Cantu”
にそれぞれ変更してみましょう。
以下の図は、EMP_NO=11のレコードを変更した例です。
(16)ApplyUpdateを実行する
DBGrid内のデータをいくら変更しても、それだけでは実際のInterBaseのデータベースに対して変更したデータは反映されていません。
これは、FDQuery1のOpenメソッドで取得した結果セットをローカルにキャッシュしているため、DBGridのデータのいくら変更してもキャッシュしたデータを変更しているに過ぎません。
[ApplyUpdateを実行]ボタンを押してください。
そして[データセットを開く]ボタンを押して、データセットを読み直してみましょう。
以上でこのセクションの演習は終了となります。
さすがに、全くコードを書かずにアプリケーション作成・・とはいきませんが、これまで説明してきましたドラッグ&ドロップでコンポーネントを配置し、プロパティを変更する操作に加えて必要最小限のコードを実装するだけで、実際のアプリケーションを構築することができます。今回の演習は、VCLアプリケーションを例にしましたが、モバイルアプリケーションやサーバーサイドのアプリケーションを作成する場合も同じ手法で進めることができます。
次回は、開発プロジェクトをもう一段効率化するための手法として、データモジュールの活用方法について説明いたします。
Design. Code. Compile. Deploy.
Start Free Trial Upgrade Today
Free Delphi Community Edition Free C++Builder Community Edition