DelphiでFireDACを使う（コード編）

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

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

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

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

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

```Delphi
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データベースを指定し、使い終わったら破棄します。一連の流れは、こんな感じです。

```Delphi
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文なども同様です。

```Delphi
connection.ExecSQL('DROP TABLE IF EXISTS [table];');
```
結果を1個だけ返すSQL文も、TFDConnectionオブジェクトから直接発行できます。

```Delphi
result := connection.ExecSQLScalar('SELECT max(id) FROM [table]');
```
# クエリセットを使う

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

```Delphi
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メソッドです。
これが基本的な形でしょう。

```Delphi
……
 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オブジェクトからの情報をデバッグ出力に流します。

```Delphi
……
 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文の実行結果を確かめるなど必要でしょう。

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