さまざまなコンポーネントとドライバーを使用したFirebird DBMSのアプリケーションの作成:FireDac

この記事では、FireDacアクセスコンポーネントとDelphi XE5環境を使用して、Firebird DBMS用のアプリケーションを作成するプロセスについて説明します。 FireDacは、Delphi XE3以降のデータベースアクセスコンポーネントの標準セットです。



このアプリケーションは、次の図に示すデータベースモデルで動作します。



図








この記事の最後には、このモデルでデータベースを作成するプロセスを説明する他の記事へのリンクと、データベース作成スクリプトへのリンクがあります。

注意!



このモデルは単なる例です。 サブジェクト領域はより複雑な場合もあれば、まったく異なる場合もあります。 この記事で使用するモデルは、コンポーネントの操作の説明、データモデルの作成と変更の説明が煩雑にならないように、可能な限り簡素化されています。



新しいプロジェクトを作成します。ファイル->新規-> VCLフォームアプリケーション-Delphi。 新しいプロジェクトで、新しい日付モジュールFile-> New-> Otherを追加し、表示されるウィザードでDelphi Projects-> Delphi Files-> Data Moduleを選択します。 この日付モジュールは、プロジェクトのメインモジュールです。 データを処理する必要があるすべてのフォームで使用できるグローバルアクセスコンポーネントのインスタンスが含まれます。 たとえば、そのようなコンポーネントはTFDConnectionです。



TFDConnectionコンポーネント



TFDConnectionコンポーネントは、さまざまなタイプのデータベースへの接続を提供します。 残りのFireDacコンポーネントのConnectionプロパティでこのコンポーネントのインスタンスを指定します。 どのタイプのデータベースに接続するかは、DriverNameプロパティの値によって異なります。 Firebirdにアクセスするには、このプロパティをFBに設定する必要があります。 接続でどのアクセスライブラリを使用する必要があるかを知るために、メインの日付モジュールにTFDPhysFBDriverLinkコンポーネントを配置します。 そのVendorLibプロパティにより、クライアントライブラリへのパスを指定できます。 指定されていない場合、Firebirdへの接続は、システムに登録されているライブラリ(system32など)を介して行われ、場合によっては望ましくないことがあります。



クライアントライブラリパス



アプリケーションフォルダーにあるfbclientフォルダーに必要なアクセスライブラリを配置します。 これを行うには、OnCreateイベントのコードでモジュールの日付に次のコードを記述します。



//      xAppPath := ExtractFileDir(Application.ExeName) + PathDelim; FDPhysFBDriverLink.VendorLib := xAppPath + 'fbclient' + PathDelim + 'fbclient.dll';
      
      







重要!



32ビットアプリケーションをコンパイルする場合は、32ビットfbclient.dllライブラリを使用する必要があります。 64ビットの場合-64ビット。 fbclient.dllファイルに加えて、msvcp80.dllおよびmsvcr80.dllライブラリ(Firebird 2.5の場合)、およびmsvcp100.dllおよびmsvcr100.dll(Firebird 3.0の場合)を同じフォルダに配置することが望ましいです。 これらのライブラリは、binサブフォルダー(Firebird 2.5)またはサーバーのルートフォルダー(Firebird 3.0)にあります。



アプリケーションが自身のfirebirdエラーを正しく表示するには、firebird.msgファイルもコピーする必要があります。 Firebird 2.5以前のバージョンでは、クライアントライブラリディレクトリの1つ上のレベル、つまり この例では、アプリケーションディレクトリにあります。 Firebird 3の場合、クライアントライブラリディレクトリにある必要があります。 fbclientディレクトリ内。





Firebirdサーバーがインストールされていなくてもアプリケーションを動作させる必要がある場合、つまり 埋め込みモードでは、Firebird 2.5の場合、fbclient.dllをfbembed.dllに置き換える必要があります。 必要に応じて、ライブラリ名をアプリケーションの構成ファイルに移動できます。 Firebird 3.0の場合、何も変更する必要はありません(操作モードは、firebird.conf / databases.confファイルの接続文字列とProvidersパラメーターの値に依存します)。



ヒント



アプリケーションが埋め込みモードのFirebirdで動作する場合でも、フルサーバーで開発する方が便利です。 事実、Embedded Firebirdモードでは、アプリケーションと同じアドレス空間で動作するため、アプリケーションでエラーが発生した場合に望ましくない結果を招く可能性があります。 さらに、開発時には、Delphi環境とアプリケーションは、Embeddedを使用する個別のアプリケーションです。 バージョン2.5より前は、1つのデータベースを同時に使用できません。



接続オプション



TFDConnectionコンポーネントには、Paramsプロパティにデータベースに接続するためのパラメーター(ユーザー名、パスワード、接続文字セットなど)が含まれています。 TFDConnectionプロパティエディターを使用する場合(コンポーネントをダブルクリック)、前述のプロパティが自動的に入力されます。 これらのプロパティのセットは、データベースのタイプによって異なります。



パラメータ 予定
プール済み 接続プールが使用されているかどうか。
データベース Firebirdサーバーのaliases.conf(またはdatabases.conf)構成ファイルで定義されたデータベースまたはそのエイリアスへのパス。
ユーザー名 ユーザー名
パスワード パスワード
OSAuthent オペレーティングシステムで認証が使用されていますか?
プロトコル 接続プロトコル。 次の値が許可されます。

  • ローカル-ローカルプロトコル。
  • NetBEUI-名前付きパイプ
  • SPX-最新バージョンではサポートされていません。
  • TCPIP-TCP / IP。


サーバー サーバー名またはそのIPアドレス。 サーバーが非標準ポートで実行されている場合は、たとえばlocalhost / 3051のように、スラッシュを使用してポートも指定する必要があります。
SQLDialect 方言。 データベースの方言と一致する必要があります。
役割名 ロールの名前。
キャラクターセット 接続文字セットの名前。


FDConnection_params








追加のプロパティ:



接続済み -データベースへの接続を制御するか、接続のステータスを確認します。 他のFireDacコンポーネントのウィザードが機能するには、このプロパティをTrueに設定する必要があります。 アプリケーションが認証データを要求する必要がある場合、アプリケーションをコンパイルする前にこのプロパティをFalseにリセットすることを忘れないことが重要です。



LoginPrompt-接続の試行時にユーザー名とパスワードを要求するかどうか。



トランザクション -TFDTransactionのコンポーネント。さまざまなTFDConnection操作を実行するためのデフォルトとして使用されます。 このプロパティが明示的に割り当てられていない場合、TFDConnectionはTFDTransactionのインスタンスを独自に作成し、そのパラメーターはTxOptionsプロパティで指定できます。



UpdateTransaction-同じ名前のTFDQueryコンポーネントプロパティのデフォルトとして使用されるTFDTransactionコンポーネント。 このプロパティが明示的に割り当てられていない場合、Transactionプロパティの値が使用されます。



ユーザー名とパスワードを除く接続パラメーターは通常、アプリケーションの操作中に変更されないため、構成ファイルから読み取ります。



  //    xIniFile := TIniFile.Create(xAppPath + 'config.ini'); try xIniFile.ReadSectionValues('connection', FDConnection.Params); finally xIniFile.Free; end;
      
      







config.iniファイルには、およそ次の行が含まれています。

 [connection] DriverID=FB Protocol=TCPIP Server=localhost/3051 Database=examples OSAuthent=No RoleName= CharacterSet=UTF8
      
      







接続セクションの内容は、ウィザードの後に​​TFDConnectionコンポーネントのParamsプロパティの内容をコピーすることにより取得できます。

発言



実際、一般的な設定は通常%AppData%\ Manufacture \ AppNameにあり、アプリケーションインストーラーによって保存されます。 ただし、開発中は、設定ファイルをアプリケーションフォルダーなどのより近くに配置すると便利です。



アプリケーションがProgram Filesフォルダーにインストールされていて、構成ファイルが同じ場所にある場合、このファイルはProgram Dataで仮想化されるか、変更とそれに続く新しい設定の読み取りに問題があることに注意してください。



データベース接続



データベースに接続するには、TFDConnectionコンポーネントのConnectedプロパティをTrueに変更するか、Openメソッドを呼び出す必要があります。 最後の方法では、ユーザー名とパスワードをパラメーターとして渡すことができます。 このアプリケーションでは、標準のデータベース接続ダイアログを置き換えます。 登録情報を3回まで入力するときに間違いを犯す機会を与え、その後、アプリケーションは閉じられます。 これを行うには、メインデータモジュールのOnCreateイベントハンドラーに次のコードを記述します。



  //   3    ,    xLoginCount := 0; xLoginPromptDlg := TLoginPromptForm.Create(Self); while (xLoginCount < MAX_LOGIN_COUNT) and (not FDConnection.Connected) do begin try if xLoginPromptDlg.ShowModal = mrOK then FDConnection.Open( xLoginPromptDlg.UserName, xLoginPromptDlg.Password) else xLoginCount := MAX_LOGIN_COUNT; except on E: Exception do begin Inc(xLoginCount); Application.ShowException(E); end end; end; xLoginPromptDlg.Free; if not FDConnection.Connected then Halt;
      
      







TFDTransactionコンポーネント



TFDTransactionコンポーネントは、トランザクションを明示的に処理するように設計されています。



Firebirdのクライアント部分では、トランザクションのコンテキストでのみアクションを実行できます。 したがって、TFDTransaction.StartTransactionを明示的に呼び出さずにデータにアクセスできた場合、FireDacの腸のどこかでこの呼び出しが自動的に発生しました。 この動作はお勧めできません。 アプリケーションがデータベースを正しく操作するには、トランザクションを手動で管理する、つまり、TFDTransactionコンポーネントのStartTransaction、Commit、およびRollbackメソッドを明示的に呼び出すことが望ましいです。



パラメータ 予定
接続 FDConnectionコンポーネントとの通信。
Options.AutoCommit トランザクションの自動開始と自動終了を制御します。 デフォルト値はTrueです。



このプロパティの値がTrueに設定されている場合、FireDACは次のことを行います。

  • 各SQLコマンドが実行される前にトランザクションが開始され(必要な場合)、SQLコマンドが実行された後にトランザクションが終了します。 コマンドが成功した場合、トランザクションはCOMMITとして完了します。それ以外の場合はROLLBACKします。
  • アプリケーションがStartTransactionメソッドを呼び出すと、トランザクションがコミットまたはロールバックとして完了するまで、自動トランザクション管理は無効になります。


Firebirdでは、自動トランザクション管理はFireDACコンポーネント自体によってエミュレートされます。

Options.AutoStart トランザクションの自動開始を制御します。 デフォルトはTrueです。
Options.AutoStop トランザクションの自動完了を制御します。 デフォルトはTrueです。
Options.DisconnectAction トランザクションがアクティブな場合、接続が閉じられたときに実行されるアクション。 デフォルト値はxdCommitです。 次のオプションが可能です。

  • xdNone-何も行われません。 アクションはDBMSに委ねられます。
  • xdCommit-トランザクションの確認。
  • xdRollback-トランザクションのロールバック。


他のアクセスコンポーネントでは、同様のxdRollbackプロパティのデフォルト値は次のとおりです。 したがって、このプロパティを実際に必要な値に手動で設定する必要があります。

Options.EnableNested ネストされたトランザクションを管理します。 デフォルト値はTrueです。



トランザクションがアクティブな場合、次のStartTransaction呼び出しはネストされたトランザクションを作成します。 FireDACは、DBMSがネストされたトランザクションを明示的にサポートしていない場合、セーブポイントを使用してネストされたトランザクションをエミュレートします。 ネストされたトランザクションを無効にするには、EnableNestedをFalseに設定すると、StartTransactionの次の呼び出しで例外がスローされます。



Firebirdは、ネストされたトランザクションを明示的にサポートしていません。

Options.Isolation トランザクション分離レベルを定義します。 これは、トランザクションの最も重要なプロパティです。 デフォルト値はxiReadCommitedです。 次のオプションが可能です。

  • xiUnspecified-DBMSのデフォルトの分離レベルが使用されます(FirebirdではSNAPSHOT、つまり、読み取りと書き込みの同時実行待機パラメーターを使用);
  • xiDirtyRead-このレベルの分離はFirebirdには存在しないため、代わりにREAD COMMITEDが使用されます。
  • xiReadCommited-コミットレベルの読み取りを読み取ります。 Firebirdでは、このようなトランザクションは読み取りと読み取りから始まります。read_committedrec_version nowait;
  • xiRepeatableRead-このレベルの分離はFirebirdには存在しないため、代わりにSNAPSHOTが使用されます。
  • xiSnapshot-スナップショット分離レベル。 Firebirdでは、このようなトランザクションは読み取り書き込み同時実行の待機パラメーターで始まります。
  • xiSerializable-シリアライズ可能な分離レベル。 実際、Firebirdにはこの分離レベルのトランザクションはありませんが、読み取り書き込み一貫性待機パラメーターでトランザクションを開始することでエミュレートされます。


Options.Params DBMS固有のトランザクションパラメータ。 現在、FirebirdおよびInterbaseでのみ使用されています。 可能な値:

  • 読む
  • 書きます
  • read_committed
  • 並行性
  • 一貫性
  • 待って
  • 待って
  • rec_version
  • rec_versionなし


Options.ReadOnly トランザクションが読み取り専用かどうかを示します。 デフォルトはFalseです。 Trueに設定すると、現在のトランザクション内での変更は不可能になります。この場合、Firebirdではトランザクションパラメーターに読み取り値がありません。



このプロパティをTrueに設定すると、DBMSはリソースの使用を最適化できます。



他のDBMSとは異なり、Firebirdでは、1つの接続に関連付けられたTFDTransactionコンポーネントをいくつでも使用できます。 このアプリケーションでは、すべてのディレクトリとオンラインジャーナルに共通の読み取りトランザクションを1つ、各ディレクトリ/ジャーナルに書き込みトランザクションを1つ使用します。



このアプリケーションでは、トランザクションの自動開始と自動完了に依存しないため、すべてのトランザクションでOptions.AutoCommit = False、Options.AutoStart = False、Options.AutoStop = Falseになります。



読み取りトランザクションはすべてのディレクトリとジャーナルに共通であるため、メインの日付モジュールに配置すると便利です。 通常の作業(グリッドにデータを表示するなど)では、分離モードREAD COMMITED(Options.Isolation = xiReadCommited)が通常使用されます。 クエリを再実行(データの再読み込み)するだけで、トランザクションが他の人のコミットされたデータベースの変更を見ることができます。 このトランザクションは読み取り専用であるため、Options.ReadOnlyプロパティをTrueに設定します。 したがって、トランザクションはread_commited rec_versionパラメーターを読み取ります。 Firebirdでこのようなパラメーターを持つトランザクションは、他のトランザクションをブロックしたり、データベース内のガベージの蓄積に影響を与えたりすることなく、任意の長い時間(日、週、月)開くことができます(実際、そのようなトランザクションはコミットされたサーバーで開始されるため)。



発言



データの再読み込み中にREAD COMMITED分離モードを使用したトランザクションでは、コミットされたすべての新しい変更が表示されるため、このようなトランザクションはレポートに使用できません(特に複数の連続クエリを使用する場合)。



レポートの場合、SNAPSHOT分離モードで短い読み取り専用トランザクションを使用することをお勧めします(Options.Isolation = xiSnapshotおよびOptions.ReadOnly = True)。 この例では、レポートの操作は考慮されていません。





メインデータモジュールのOnCreateイベントでtrRead.StartTransactionを呼び出して、データベースへの接続に成功した直後に読み取りトランザクションを開始し、メインデータモジュールのOnDestroyイベントでtRead.Commitを呼び出して接続を閉じる前に完了します。 Options.DisconnectActionプロパティの値は、デフォルトではxdCommitであり、読み取り専用トランザクションに適しています。



書き込みトランザクションは、ディレクトリ/ジャーナルごとに個別になります。 目的の雑誌に直接関連するフォームに配置します。 書き込みトランザクションは、ガベージコレクションを許可しない最も古いアクティブトランザクションを保持しないように、できるだけ短くする必要があります。これにより、パフォーマンスが低下します。 書き込みトランザクションは非常に短いため、SNAPSHOT分離レベルを使用できます。 したがって、書き込みトランザクションにはoptions.ReadOnly = FalseおよびOptions.Isolation = xiSnapshotが含まれます。 トランザクションを記述する場合、Options.DisconnectActionプロパティの値はデフォルトでは適切ではないため、xdRollbackに設定する必要があります。



データセット



FDQuery、FDTable、FDStoredProc、FDCommandコンポーネントを使用してFireDacのデータを操作できますが、FDCommandはデータセットではありません。



TFDQuery、TFDTable、TFDStoredProcは、TFDRdbmsDataSetから継承されます。 データベースを直接操作するためのデータセットに加えて、FireDacには、TClientDataSetに類似したメモリ内のデータセットを操作するように設計されたTFDMemTableコンポーネントもあります。



データセットを操作するための主要コンポーネントはTFDQueryです。 このコンポーネントの機能は、ほとんどすべての目的に十分です。 コンポーネントTFDTableおよびTFDStoredProcは、わずかに拡張または切り捨てられた単なる変更です。 アプリケーションではそれらを考慮して適用しません。 必要に応じて、FireDacのドキュメントでそれらを読むことができます。



コンポーネントの目的は、SELECTステートメントによって選択されたレコードをバッファリングして、グリッド内のこのデータを表すことと、レコードの「編集可能性」を確保することです(バッファ(グリッド)に現在あります)。 IBX.IBDataSetコンポーネントとは異なり、FDQueryコンポーネントにはRefreshSQL、InsertSQL、UpdateSQL、およびDeleteSQLプロパティが含まれていません。 代わりに、UpdateObjectプロパティに設定されているFDUpdateSQLコンポーネントによって「編集可能」が提供されます。



発言



場合によっては、UpdateObjectプロパティを設定せずにFDQueryコンポーネントを編集可能にし、Insert / Update / Deleteクエリを作成し、UpdateOptions.RequestLive = Trueプロパティを設定するだけで、クエリの変更が自動的に生成されます。 ただし、このアプローチにはメインのSELECTクエリに関する多くの制限があるため、それに依存しないでください。





パラメータ 予定
接続 FDConnectionコンポーネントとの通信。
マスターソース 詳細として使用されるFDQueryのマスターデータソース(TDataSource)へのリンク。
取引 SQLプロパティで指定されたクエリが実行されるトランザクション。 プロパティが指定されていない場合、デフォルトのトランザクションが接続に使用されます。
更新オブジェクト UpdateOptions.RequestLive = Trueを設定するときにSELECTクエリが変更クエリを自動的に生成するための要件を満たさない場合、「編集可能な」データセットを提供するFDUpdateSQLコンポーネントとの通信。
UpdateTransaction 変更クエリが実行されるトランザクション。 プロパティが指定されていない場合、Transactionプロパティのトランザクションが使用されます。
UpdateOptions.CheckRequired CheckRequiredプロパティがTrueに設定されている場合、FireDacは対応するフィールドのRequiredプロパティを制御します。 NOT NULL制約のあるフィールド。 デフォルトはTrueに設定されています。



CheckRequired = Trueで、Required = Trueプロパティを持つフィールドに値が割り当てられていない場合、Postメソッドが呼び出されたときに例外がスローされます。 このフィールドの値を後でBEFOREトリガーに割り当てることができる場合、これは望ましくない場合があります。

UpdateOptions.EnableDelete データセットからレコードを削除できるかどうかを決定します。 EnableDelete = Falseの場合、Deleteメソッドが呼び出されると例外がスローされます。
UpdateOptions.EnableInsert データセットへのレコードの挿入を許可するかどうかを決定します。 EnableInsert = Falseの場合、Insert / Appendメソッドを呼び出すと例外がスローされます。
UpdateOptions.EnableUpdate データセット内のレコードの変更を許可するかどうかを決定します。 EnableUpdate = Falseの場合、Editメソッドを呼び出すと例外がスローされます。
UpdateOptions.FetchGeneratorsPoint UpdateOptions.GeneratorNameプロパティまたは自動インクリメントフィールドAutoGenerateValue = arAutoIncのGeneratorNameプロパティで指定された次のジェネレータ値を取得するタイミングを制御します。 次のオプションがあります。

  • gpNone-ジェネレーターの値は取得されません。
  • gpImmediate-次のジェネレータ値は、Insert / Appendメソッドを呼び出した直後に取得されます。
  • gpDeffered-次のジェネレータ値は、データベースに新しいレコードが公開される前に取得されます。 PostまたはApplyUpdatesメソッドの実行中。


デフォルト値はgpDefferedです。

UpdateOptions.GeneratorName 次の自動インクリメントフィールド値を取得するジェネレータの名前。
UpdateOptions.ReadOnly データセットが読み取り専用かどうかを示します。 デフォルトはFalseです。 このプロパティの値がTrueに設定されている場合、EnableDelete、EnableInsert、およびEnableUpdateプロパティの値は自動的にFalseに設定されます。
UpdateOptions.RequestLive RequestLiveをTrueに設定すると、リクエストが「ライブ」になります。 可能であれば編集可能。 この場合、挿入/更新/削除クエリが自動的に生成されます。 このオプションは、BDEとの後方互換性のために導入されたSELECTクエリに多くの制限を課すため、お勧めしません。
UpdateOptions.UpdateMode レコードの変更を確認します。 このプロパティにより、ユーザーが「長時間」レコードを編集していて、別のユーザーが同じレコードを編集して保存することができる場合に、更新の「重複」の可能性を制御できました。 つまり、編集段階の最初のユーザーは、レコードが既に(おそらく複数回)変更されていることすら知らず、自分でこれらの更新を「ワイプ」できます。

  • upWhereAll-主キーでレコードの存在を確認し、すべての列で古い値を確認します。 例えば

     update table set ... where pkfield = :old_ pkfield and client_name = :old_client_name and info = :old_info ...
          
          





    つまり、この場合、リクエストがレコード内の情報を変更するのは、誰もレコードを変更できなかった場合のみです。

    これは、列の値の間に相互依存関係がある場合(たとえば、最低賃金と最高賃金など)に特に重要です。

  • upWhereCahnged-主キーによるレコードの存在の確認+可変列のみの古い値の確認。

     update table set ... where pkfield = :old_pkfield and client_name = :old_client
          
          





  • upWhereKeyOnly(デフォルト)-主キーによってレコードの存在を確認します。


最後のチェックは、UpdateSQL用に自動的に生成されたクエリに対応しています。 , where . , , upWhereChanged update table set… — , set , . , UpdateSQL.



, ProviderFlags .

CachedUpdates , . True, (Insert/Post, Update/Post, Delete) , .



, ApplyUpdates. . False.

SQL SQL . SELECT , Open. Execute ExecSQL.




TFDUpdateSQL



TFDUpdateSQLコンポーネントを使用すると、生成されたSQLコマンドをオーバーライドして、データセットを自動的に更新できます。TFDQuery、TFDTable、TFDStoredProcコンポーネントの更新に使用できます。TFDQueryおよびTFDTableコンポーネントでは、TFDUpdateSQLの使用はオプションです。これらのコンポーネントは、データセットからDBMSに更新を発行するためのコマンドを自動的に生成できるためです。TFDStoredProcデータセットを更新するには、TFDUpdateSQLを使用する必要があります。アプリケーションで実行されるリクエストを完全に制御するために、最も単純な場合でも常に使用することをお勧めします。



設計段階でSQLコマンドを指定するには、コンポーネントをダブルクリックして呼び出される設計時のTFDUpdateSQLエディターを使用します。



発言



FireDac , (TFDConnection.Connected = True) (TFDTransaction.Options.AutoStart = True). . , , TFDConnection SYSDBA. TFDConnection.Connected . , .



FDUpdateSQL_Generate








Generate Insert/Update/Delete/Refresh . , , , , «Generate SQL».



, «SQL Commands», .



FDUpdateSQL_SQLCommands








発言



product_id Updating Fields, insert. , ( ), IDENTITY ( Firebird 3.0). , PRODUCT_ID RETURNING INSERT.





[オプション]タブには、クエリの生成に影響する可能性のあるプロパティが含まれています。これらのプロパティはTFDUpdateSQLコンポーネント自体には適用されませんが、UpdateObjectプロパティに現在のTFDUpdateSQLがあるデータセットのUpdateOptionsプロパティへのリンクです。これは単に便宜上行われています。



パラメータ 予定
接続 FDConnectionコンポーネントとの通信。
DeleteSQL レコードを削除するSQLクエリ。
フェッチロウ 1つの現在の(更新、挿入された)レコードを返すSQLクエリ。
InsertSQL レコードを挿入するSQLクエリ。
LockSQL 1つの現在のレコードをロックするSQLクエリ。(ロック付き更新の場合)。
ModifySQL レコードを変更するSQLクエリ。
ロック解除 現在のレコードをロック解除するSQLクエリ。Firebirdは適用されません。




, TFDUpdateSQL Transaction. , , , TFDRdbmsDataSet.



TFDCommand



TFDCommand SQL . TDataSet, SQL , .



パラメータ 予定
Connection FDConnection.
Transaction , SQL .
CommandKind .

  • skUnknown – . ;
  • skStartTransaction – ;
  • skCommit – ;
  • skRollback – ;
  • skCreate – CREATE … ;
  • skAlter – ALTER … ;
  • skDrop – DROP … ;
  • skSelect – SELECT ;
  • skSelectForLock – SELECT … WITH LOCK ;
  • skInsert – INSERT … ;
  • skUpdate – UPDATE … ;
  • skDelete – DELETE … ;
  • skMerge – MERGE INTO …
  • skExecute – EXECUTE PROCEDURE EXECUTE BLOCK;
  • skStoredProc – ;
  • skStoredProcNoCrs – ;
  • skStoredProcWithCrs – .


通常、コマンドのタイプは、SQLクエリのテキストによって自動的に決定されます。

CommandText SQLクエリのテキスト。




ディレクトリの作成



このアプリケーションでは、商品のディレクトリと顧客のディレクトリの2つのディレクトリを作成します。各ディレクトリは、TDBGridグリッド、TDataSourceデータソース、TFDTransactionトランザクションを書き込むTFDQueryデータセットを持つフォームです。



Customers








発言



trReadコンポーネントは、フォーム上ではなくdmMainモジュール内にあるため、表示されません。





顧客のディレクトリの例に関するリファレンスブックの作成を検討してください。



TFDQueryコンポーネントをqryCustomersという名前のフォームに配置します。このデータセットは、DataSourceのDataSetプロパティで指定されます。 Transactionプロパティで、プロジェクトのメインデータモジュールで作成されたReadOnlyトランザクションtrReadを指定します。 UpdateTransactionプロパティでは、Connectionプロパティで、メインデータモジュールにある接続であるtrWriteトランザクションを指定します。 SQLプロパティで、次のクエリを作成します。



 SELECT customer_id, name, address, zipcode, phone FROM customer ORDER BY name
      
      







書き込みトランザクションtrWriteは、可能な限り短くし、SNAPSHOT分離モードにする必要があります。トランザクションの自動開始と完了には依存しませんが、トランザクションを明示的に開始および終了します。したがって、トランザクションには次のプロパティが必要です



。Options.AutoStart= False

Options.AutoCommit = False

Options.AutoStop = False

Options.DisconnectAction = xdRollback

Options.Isolations = xiSnapshot

Options.ReadOnly = False



実際、単純なINSERT / UPDATE / DELETEに対してSNAPSHOT分離モードを設定する必要はありません。ただし、テーブルに複雑なトリガーがある場合、または単純なINSERT / UPDATE / DELETEクエリの代わりにストアドプロシージャが呼び出される場合は、SNAPSHOT分離レベルを使用することをお勧めします。



実際、READ COMMITEDの分離レベルでは、単一トランザクション内のオペレーターのアトミック性は保証されません(ステートメントの読み取り一貫性)。したがって、SELECTステートメントは、クエリの開始後にデータベースに落ちたデータを返すことができます。原則として、トランザクションが短い場合、SNAPSHOT分離モードをほぼ常に推奨できます。



データセットを編集できるようにするには、InsertSQL、ModifySQL、DeleteSQL、およびFetchRowSQLプロパティを入力する必要があります。これらのプロパティはウィザードで生成できますが、その後、編集が必要になる場合があります。たとえば、RETURNING句を追加したり、一部の列の変更を削除したり、自動的に生成されたリクエストを置き換えてストアドプロシージャを呼び出すこともできます。



InsertSQL:



 INSERT INTO customer (customer_id, name, address, zipcode, phone) VALUES (:new_customer_id, :new_name, :new_address, :new_zipcode, :new_phone)
      
      







ModifySQL:



 UPDATE customer SET name = :new_name, address = :new_address, zipcode = :new_zipcode, phone = :new_phone WHERE (customer_id = :old_customer_id)
      
      







DeleteSQL:



 DELETE FROM customer WHERE (customer_id = :old_customer_id)
      
      







FetchRowSQL:



 SELECT customer_id, name, address, zipcode, phone FROM customer WHERE customer_id = :old_customer_id
      
      







このリファレンスでは、レコードをテーブルに挿入する前にジェネレーターの値を取得します。これを行うには、TFDQueryコンポーネントのプロパティを以下の値に設定します:UpdateOptions.GeneratorName = GEN_CUSTOMER_IDおよびUpdateOptions.AutoIncFields = CUSTOMER_ID。 RETURNING句を使用してINSERT要求を実行した後、ジェネレーターの値(自動インクリメントフィールド)が返される別の方法があります。このメソッドは後で表示されます。



新しいレコードを追加して既存のレコードを編集するには、通常、モーダルフォームを使用します。モーダルフォームを閉じると、mrOKの結果、データベースに変更が加えられます。通常、DBAwareコンポーネントを使用してこのようなフォームを作成します。これにより、現在のレコードにフィールドの値を表示し、挿入/編集モードでデータセットの現在のレコードをすぐに変更できます。投稿する。ただし、書き込みトランザクションを開始することによってのみ、データセットを挿入/編集モードに切り替えることができます。したがって、誰かが新しいエントリを作成するためにフォームを開き、このフォームを閉じずにランチに出た場合、従業員がランチから戻ってフォームを閉じるまでアクティブなトランザクションがハングします。これにより、アクティブなトランザクションがガベージコレクションを保持し、後でパフォーマンスが低下します。この問題は、次の2つの方法のいずれかで解決できます。

  1. CachedUpdatesモードを使用します。このモードでは、変更を加えている間、トランザクションを非常に短い時間だけアクティブに保つことができます。
  2. DBAwareコンポーネントを拒否します。ただし、このパスには追加の努力が必要です。


両方の方法の適用を示します。参考のために、最初の方法を使用する方がはるかに便利です。ベンダーレコードの編集コードを検討する

 procedure TCustomerForm.actEditRecordExecute(Sender: TObject); var xEditor: TEditCustomerForm; begin xEditor := TEditCustomerForm.Create(Self); try xEditor.OnClose := CustomerEditorClose; xEditorForm.DataSource := DataSource; xEditor.Caption := 'Edit customer'; qryCustomer.CachedUpdates := True; qryCustomer.Edit; xEditor.ShowModal; finally xEditor.Free; end; end;
      
      





このコードは、データセットを編集モードにする前に、CachedUpdatesモードに設定し、すべての編集処理ロジックがモーダル形式で行われることを示しています。

 procedure TCustomerForm.CustomerEditorClose (Sender: TObject; var Action: TCloseAction); begin if TForm(Sender).ModalResult <> mrOK then begin //    qryCustomer.Cancel; qryCustomer.CancelUpdates; //        qryCustomer.CachedUpdates := False; //     Action := caFree; Exit; end; try //       qryCustomer.Post; //   trWrite.StartTransaction; //       if (qryCustomer.ApplyUpdates = 0) then begin //     qryCustomer.CommitUpdates; //    trWrite.Commit; end else begin raise Exception.Create(qryCustomer.RowError.Message); end; qryCustomer.CachedUpdates := False; Action := caFree; except on E: Exception do begin //   if trWrite.Active then trWrite.Rollback; Application.ShowException(E); //   ,     Action := caNone; end; end; end;
      
      





このコードは、[OK]ボタンが押されるまで、書き込みトランザクションがまったく開始されないことを示しています。したがって、書き込みトランザクションは、データセットバッファからデータベースへのデータ転送の期間のみアクティブです。バッファに保存するレコードは1つだけなので、必要に応じて、トランザクションは非常に短時間アクティブになります。



商品のディレクトリは、顧客のディレクトリと同様に作成されます。ただし、その中で、自動インクリメント値を取得する別の方法を示します。



主なリクエストは次のようになります。

 SELECT product_id, name, price, description FROM product ORDER BY name
      
      





コンポーネントプロパティTFDUpdateSQL.InsertSQLには、次のクエリが含まれます。

 INSERT INTO PRODUCT (NAME, PRICE, DESCRIPTION) VALUES (:NEW_NAME, :NEW_PRICE, :NEW_DESCRIPTION) RETURNING PRODUCT_ID
      
      





このクエリにはRETURNING句が含まれており、BEFORE INSERTトリガーでPRODUCT_IDフィールドの値を変更した後に値を返します。この場合、UpdateOptions.GeneratorNameプロパティの値を設定しても意味がありません。また、このプロパティの値は直接入力されないため、PRODUCT_IDフィールドはRequired = FalseおよびReadOnly = Trueに設定する必要があります。それ以外の場合、すべてはメーカーディレクトリ用に編成されたものとほぼ同じです。



雑誌を作成する



«-». .



- – , (, , …), - , , .. : , — . , TDBGrid, TDataSource, TFDQuery. qryInvoice, qryInvoiceLine.



両方のデータセットのTransactionプロパティで、メインプロジェクトデータモジュールで作成されたReadOnlyトランザクションtrReadを指定します。 UpdateTransactionプロパティで、Connectionプロパティ(メインデータモジュールにある接続)でtrWriteトランザクションを指定します。



ほとんどのログには、ドキュメントが作成された日付のフィールドが含まれています。選択されるデータの量を減らすために、通常、クライアントに送信されるデータの量を減らすために、作業期間などの概念を導入するのが慣習です。作業期間は、作業文書が必要な日付範囲です。アプリケーションには複数のログが含まれている可能性があるため、作業期間の開始日と終了日を含む変数をグローバルdmMainデータモジュールに配置することは理にかなっています。アプリケーションが開始されると、通常、作業期間は現在の四半期の開始日と終了日によって初期化されます(他のオプションがある場合があります)。アプリケーション中に、ユーザーの要求に応じて勤務期間を変更できます。



Invoices








最も最近入力された文書が最も頻繁に必要とされるため、日付の逆順に並べ替えることは理にかなっています。上記に基づいて、qryInvoiceデータセットのSQLプロパティでは、クエリは次のようになります。



 SELECT invoice.invoice_id AS invoice_id, invoice.customer_id AS customer_id, customer.NAME AS customer_name, invoice.invoice_date AS invoice_date, invoice.total_sale AS total_sale, IIF(invoice.payed=1, 'Yes', 'No') AS payed FROM invoice JOIN customer ON customer.customer_id = invoice.customer_id WHERE invoice.invoice_date BETWEEN :date_begin AND :date_end ORDER BY invoice.invoice_date DESC
      
      







このデータセットを開くとき、クエリパラメータを初期化する必要があります。



  qryInvoice.ParamByName('date_begin').AsSqlTimeStamp := dmMain.BeginDateSt; qryInvoice.ParamByName('date_end').AsSqlTimeStamp := dmMain.EndDateSt; qryInvoice.Open;
      
      







請求書に対するすべての操作は、ストアドプロシージャを使用して実行されますが、より簡単な場合、通常のINSERT / UPDATE / DELETEクエリを使用して実行することもできます。



TFDCommandコンポーネントで、各ストアドプロシージャを個別のリクエストとして実行します。このコンポーネントはTFDRdbmsDataSetの祖先ではありません。データをバッファリングせず、最大1行の結果を返すため、データを返さないクエリのオーバーヘッドが少なくなります。ストアドプロシージャはデータの変更を実行するため、TFDCommandコンポーネントのTransactionプロパティはトランザクションtrWriteを設定する必要があります。



発言



レコードを挿入、編集、および追加するためのストアドプロシージャは、TFDUpdateSQLコンポーネントの対応するプロパティに配置することもできます。





- : , , «». - , , , . . .



qryAddInvoice.CommandText:

 EXECUTE PROCEDURE sp_add_invoice( NEXT VALUE FOR gen_invoice_id, :CUSTOMER_ID, :INVOICE_DATE )
      
      







qryEditInvoice.CommandText:

 EXECUTE PROCEDURE sp_edit_invoice( :INVOICE_ID, :CUSTOMER_ID, :INVOICE_DATE )
      
      







qryDeleteInvoice.CommandText:

 EXECUTE PROCEDURE sp_delete_invoice(:INVOICE_ID)
      
      







qryPayForInvoice.CommandText:

 EXECUTE PROCEDURE sp_pay_for_inovice(:invoice_id)
      
      







TFDUpdateSQL, qryInvoice.Refresh .



, , :

  if MessageDlg('     ?', mtConfirmation, [mbYes, mbNo], 0) = mrYes then begin //   trWrite.StartTransaction; try qryDeleteInvoice.ParamByName('INVOICE_ID').AsInteger := qryInvoice.FieldByName('INVOICE_ID').AsInteger; //    qryDeleteInvoice.Execute; //   trWrite.Commit; //     qryInvoice.Refresh; except on E: Exception do begin if trWrite.Active then trWrite.Rollback; Application.ShowException(E); end; end; end;
      
      







ディレクトリの場合のように、新しいレコードを追加して既存のレコードを編集するには、モーダルフォームを使用します。この場合、DBAwareコンポーネントは使用しません。もう1つの機能は、TButtonedEditコンポーネントを使用して顧客を選択することです。現在の顧客の名前が表示され、ボタンをクリックするだけで、グリッドを含むモーダルフォームが呼び出され、顧客が選択されます。もちろん、TDBLookupComboboxのようなものを使用することもできますが、第一に、多くの顧客が存在する可能性があり、そのようなドロップダウンリストをスクロールするのは不便です。



EditInvoice








, . TButtonedEdit :



 procedure TEditInvoiceForm.edtCustomerRightButtonClick(Sender: TObject); var xSelectForm: TCustomerForm; begin xSelectForm := TCustomerForm.Create(Self); try xSelectForm.Visible := False; if xSelectForm.ShowModal = mrOK then begin FCustomerId := xSelectForm.qryCustomer.FieldByName('CUSTOMER_ID') .AsInteger; edtCustomer.Text := xSelectForm.qryCustomer.FieldByName('NAME').AsString; end; finally xSelectForm.Free; end; end;
      
      







DBAware , .



 procedure TInvoiceForm.actEditInvoiceExecute(Sender: TObject); var xEditorForm: TEditInvoiceForm; begin xEditorForm:= TEditInvoiceForm.Create(Self); try xEditorForm.OnClose := EditInvoiceEditorClose; xEditor.Caption := ' -'; xEditorForm.InvoiceId := qryInvoice.FieldByName('INVOICE_ID').AsInteger; xEditorForm.SetCustomer(qryInvoice.FieldByName('CUSTOMER_ID').AsInteger, qryInvoice.FieldByName('CUSTOMER_NAME').AsString); xEditorForm.InvoiceDate := qryInvoice.FieldByName('INVOICE_DATE').AsDateTime; xEditorForm.ShowModal; finally xEditorForm.Free; end; end;
      
      





 procedure TEditInvoiceForm.SetCustomer(ACustomerId: Integer; const ACustomerName: string); begin FCustomerId := ACustomerId; edtCustomer.Text := ACustomerName; end;
      
      







- , , . CachedUpdates, , DBAware .



 procedure TInvoiceForm.EditInvoiceEditorClose(Sender: TObject; var Action: TCloseAction); var xEditorForm: TEditInvoiceForm; begin xEditorForm := TEditInvoiceForm(Sender); //        OK, //     .   . if xEditorForm.ModalResult <> mrOK then begin Action := caFree; Exit; end; //      trWrite.StartTransaction; try qryEditInvoice.ParamByName('INVOICE_ID').AsInteger := xEditorForm.InvoiceId; qryEditInvoice.ParamByName('CUSTOMER_ID').AsInteger := xEditorForm.CustomerId; qryEditInvoice.ParamByName('INVOICE_DATE').AsSqlTimeStamp := DateTimeToSQLTimeStamp(xEditorForm.InvoiceDate); qryEditInvoice.Execute(); trWrite.Commit; qryInvoice.Refresh; Action := caFree; except on E: Exception do begin if trWrite.Active then trWrite.Rollback; Application.ShowException(E); //   ,     Action := caNone; end; end; end;
      
      







それでは、請求書の位置に移りましょう。qryInvoiceLineデータセットに、qryInvoiceにバインドされているMasterSource = MasterSourceプロパティと、MasterFields = INVOICE_IDプロパティを設定します。SQLプロパティで、次のクエリを作成します。



 SELECT invoice_line.invoice_line_id AS invoice_line_id, invoice_line.invoice_id AS invoice_id, invoice_line.product_id AS product_id, product.name AS productname, invoice_line.quantity AS quantity, invoice_line.sale_price AS sale_price, invoice_line.quantity * invoice_line.sale_price AS total FROM invoice_line JOIN product ON product.product_id = invoice_line.product_id WHERE invoice_line.invoice_id = :invoice_id
      
      







請求書ヘッダーの場合のように、すべての変更はストアドプロシージャを使用して実行されます。ストアドプロシージャを呼び出すためのリクエストテキストを次に示します。



qryAddInvoiceLine:

 EXECUTE PROCEDURE sp_add_invoice_line( :invoice_id, :product_id, :quantity )
      
      





qryEditInvoiceLine:

 EXECUTE PROCEDURE sp_edit_invoice_line( :invoice_line_id, :quantity )
      
      





qryDeleteInvoiceLine:

 EXECUTE PROCEDURE sp_delete_invoice_line( :invoice_line_id )
      
      







ヘッダーの場合のように、新しいレコードを追加して既存のレコードを編集するためのフォームは、DBAwareを使用しません。製品を選択するには、TButtonedEditコンポーネントを使用します。TButtonedEditコンポーネントのボタンクリックハンドラコードは次のようになります。



 procedure TEditInvoiceLineForm.edtProductRightButtonClick(Sender: TObject); var xSelectForm: TGoodsForm; begin //        //         if FEditMode = emInvoiceLineEdit then Exit; xSelectForm := TGoodsForm.Create(Self); try xSelectForm.Visible := False; if xSelectForm.ShowModal = mrOK then begin FProductId := xSelectForm.qryGoods.FieldByName('PRODUCT_ID') .AsInteger; edtProduct.Text := xSelectForm.qryGoods.FieldByName('NAME').AsString; //          edtPrice.Text := xSelectForm.qryGoods.FieldByName('PRICE').AsString; end; finally xSelectForm.Free; end; end;
      
      







DBAware以外のコンポーネントを使用しているため、編集フォームを呼び出すときに、製品コード、名前、および表示コストを初期化する必要があります。



 procedure TInvoiceForm.actEditInvoiceLineExecute(Sender: TObject); var xEditorForm: TEditInvoiceLineForm; begin xEditorForm:= TEditInvoiceLineForm.Create(Self); try xEditorForm.OnClose := EditInvoiceLineEditorClose; xEditorForm.EditMode := emInvoiceLineEdit; xEditorForm.Caption := ' '; xEditorForm.InvoiceLineId := qryInvoiceLine.FieldByName('INVOICE_LINE_ID').AsInteger; xEditorForm.SetProduct(qryInvoiceLine.FieldByName('PRODUCT_ID').AsInteger, qryInvoiceLine.FieldByName('PRODUCTNAME').AsString, qryInvoiceLine.FieldByName('SALE_PRICE').AsCurrency); xEditorForm.Quantity := qryInvoiceLine.FieldByName('QUANTITY').AsInteger; xEditorForm.ShowModal; finally xEditorForm.Free; end; end;
      
      







 procedure TEditInvoiceLineForm.SetProduct(AProductId: Integer; AProductName: string; APrice: Currency); begin FProductId := AProductId; edtProduct.Text := AProductName; edtPrice.Text := CurrToStr(APrice); end;
      
      







モーダルフォームを閉じると、新しい位置を追加して既存の位置を編集する処理が実行されます。



 procedure TInvoiceForm.EditInvoiceLineEditorClose(Sender: TObject; var Action: TCloseAction); var xCustomerId: Integer; xEditorForm: TEditInvoiceLineForm; begin xEditorForm := TEditInvoiceLineForm(Sender); //        OK, //     .   . if xEditorForm.ModalResult <> mrOK then begin Action := caFree; Exit; end; //      trWrite.StartTransaction; try qryEditInvoiceLine.ParamByName('INVOICE_LINE_ID').AsInteger := xEditorForm.InvoiceLineId; qryEditInvoiceLine.ParamByName('QUANTITY').AsInteger := xEditorForm.Quantity; qryEditInvoiceLine.Execute(); trWrite.Commit; qryInvoice.Refresh; qryInvoiceLine.Refresh; Action := caFree; except on E: Exception do begin if trWrite.Active then trWrite.Rollback; Application.ShowException(E); //    .     Action := caNone; end; end; end;
      
      







まあ、それだけです。この記事が、Firebird DBMSを使用する際に、FireDacコンポーネントを使用してDelphiでアプリケーションを作成する機能を理解するのに役立つことを願っています。



完成したアプリケーションは次のとおりです。



screenshot








質問、コメント、提案は、コメントまたはPMに記入してください。



参照資料



ソースコードとサンプルデータベース



All Articles