DelphiでFireDACを使う(コード編)

データベースプログラミングというと、コントロールをフォーム配置してさぁ簡単、という話が多いのですが、それでは済まないケースの方が多いです。そういうときに、コードをゴリゴリ書いていくにはどうしたらいいか、私の経験から備忘録的にまとめてみました。

ちなみに開発環境は、すっかりユーザも少なくなったDelphi、データベースコンポーネントはFireDACです。Delphiの商品構成の見直しでFireDACがEnterprise版以上に制限されるそうですが、ローカルアクセスについては従来通り使えるようです。

話を簡単にするために、接続先のデータベースはSQLiteとします。また、必要な例外処理などは入れていないので、必要に応じてtry~exceptブロックで挟みます。

uses節にFireDACのユニットを追加

これは、何でもいいので1個、フォームにFireDACのコントロールを配置すれば自動的に追加されます。数が多いですが、適当にコピペして他のユニットにも持って行けます。

uses
 FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Error, FireDAC.UI.Intf,
 FireDAC.Phys.Intf, FireDAC.Stan.Def, FireDAC.Stan.Pool, FireDAC.Stan.Async,
 FireDAC.Phys, FireDAC.Stan.Param, FireDAC.DatS, FireDAC.DApt.Intf,
 FireDAC.DApt, FireDAC.Comp.Client, FireDAC.Comp.DataSet,
 FireDAC.Stan.ExprFuncs, FireDAC.Phys.SQLite, FireDAC.VCLUI.Wait,
 FireDAC.Comp.UI;

TFDConnectionコントロールの生成と破棄

TFDConnectionは、データベースを使うための最も基本的なコントロールです。これを動的に生成し、接続するSQLiteデータベースを指定し、使い終わったら破棄します。一連の流れは、こんな感じです。

var
 connection: TFDConnection;
 ……

begin
 ……
 connection := TFDConnection.Create(AOwner);
 connection.DriverName := 'SQLite';
 connection.Params.Clear;
 connection.Params.Add('Database=database.db');
 connection.Params.Add('DriverID=SQLite');
 connection.Open;
 ……(ここで各種のデータベース操作を行う)
 connection.Close;
 connection.Free;
 ……

直接SQL文を実行

結果を返さないSQL文は、TFDConnectionオブジェクトから直接発行できます。下記はDROP文ですが、INSERT文、ALTER文、CREATE文なども同様です。

connection.ExecSQL('DROP TABLE IF EXISTS [table];');

結果を1個だけ返すSQL文も、TFDConnectionオブジェクトから直接発行できます。

result := connection.ExecSQLScalar('SELECT max(id) FROM [table]');

クエリセットを使う

SELECT文を発行して結果をもらうなどの場合には、TFDQueryコントロールを使います。安全のために、TFDConnectionオブジェクトのConnectedプロパティを参照し、接続されている場合のみ実行しています。

var
 query: TFDQuery;

begin
 ……
 if connection.Connected then begin
   query := TFDQuery.Create(connection);
   query.Connection := connection;
   ……(ここで各種のデータベース操作を行う)
   qyery.Free;
 end;
 ……

レコードを参照する

SELECT文を発行すれば、結果セットを取得できます。
FetchOptions.RecordCountModeプロパティにcmTotalを指定すると、RecordCountプロパティの返す値が常に結果セットの全レコード数になります。SQL.Textプロパティに実行したいSQL文を入れておくのですが、コロン(:)で始めた部分(下記では:id)はパラメータidになり、あとで値を設定できます。値を設定するのは、ParamByNameメソッドです。
Openメソッドでクエリをオープン、FetchAllメソッドで結果セットを全部取得します。
あとは、RecordCountプロパティの数だけループを回します。次のレコードに移るのはNextメソッドです。現在のレコードからの値の取り出しは、FieldByNameメソッドです。
これが基本的な形でしょう。

……
 query.FetchOptions.RecordCountMode := cmTotal;
 query.SQL.Text := 'SELECT * FROM [table] WHERE [id] = :id';
 query.ParamByName('id').AsInteger := id;
 query.Open;
 query.FetchAll;
 for i := 1 to query.RecordCount do begin
   strvalue := query.FieldByName('name').AsString;
   intvalue := query.FieldByName('birth').AsInteger;
   ……
   query.Next;
 end;
 query.Close;
 ……

レコードを挿入、削除する

値を返さないクエリも、TFDQueryオブジェクトに対して実行できます。下記のように、複数のテーブルに登録する場合などは、TFDConnectionオブジェクトのStartTransactionメソッドでトランザクションを開始し、無事終われば(例外に捕捉されなければ)Commitメソッドを実行します。
例外が発生すれば、RollbackメソッドでSQL文の実行をなかったことにして、Exceptionオブジェクトからの情報をデバッグ出力に流します。

……
 connection.StartTransaction;
 try
   query.ExecSQL('INSERT INTO [table1] VALUES(1, 2, 3);');
   query.ExecSQL('INSERT INTO [table2] VALUES(4, 5, 6);');
   query.ExecSQL('INSERT INTO [table3] VALUES(7, 8, 9);');
   connection.Commit;
 except
   on E: Exception do begin
     connection.Rollback;
     OutputDebugString(PWideChar(E.ClassName+', '+E.Message));
   end;
 end;

ざっと見てきましたが、すごい基本的な形なので、データ数の多寡などはまったく気にしていません。データ量に応じてFetchOptionsプロパティの値をコントロールしたり、AffectedRowsプロパティの値を参照してINSERT文の実行結果を確かめるなど必要でしょう。

機会があれば、データベースコンポーネントの方もやってみたいと思います。

コメント