ここではデータディクショナリを利用したバリデーションについて説明します。

Oracleのデータ型と、Delphiのデータ型は必ずしも一致しません。例えば、OracleでNumber(7,2)として宣言されているフィールドを、Delphiは単なるDouble型に対応させるのが普通です。とすると、例えば10000.00のような不正な値をDelphiが許してしまい、データがOracleに渡って初めてエラーが発生することになります。

そこでDOAは、Oracleオブジェクトに関する情報をデータディクショナリビューを通じて予め取得し、不正なデータの入力を未然に防ぐためのしくみも備えています。それを調整するためのプロパティがTOracleDataSet.OracleDictionaryです。デフォルトではすべて無効になっていますので、必要に応じて機能を有効化して使ってください。

  • TOracleDataSet.OracleDictionary

    • RequiredFields: boolean: このプロパティをTrueにすると、Oracle上で、"not
      null"として宣言されているフィールドがどれかをDOAが検出し、データセットの対応フィールド(TField)のRequiredプロパティをTrueにセットします。
    • R
      angeValues: boolean: TIntegerField , TFloatField のMinValue,
      MaxValueを、データディクショナリに基づいてセットします。例えば、Number(7,2)のフィールドについてはMinValue=-99999.99,
      MaxValue=99999.99 となります
    • DisplayFormats: boolean: TIntegerField, TFloatFieldのDisplayFormat,
      EditFormatを、データディクショナリに基づいてセットします。例えば、Number(7,2)のフィールドについて
      DisplayFormat=
      ##,##0.00, EditFormat='####0.00'となります
    • FieldKinds: boolean:
      Trueにすると、各フィールドのFieldTypeプロパティをデータディクショナリに基づいてセットします。更新対象テーブルのフィールドのみがテーブルの物理項目(fkData)として扱われ,
      その他のフィールドは計算結果フィールド(fkInternalCalc)としてマークされます。
    • DefaultValues: boolean:
      通常、フィールドのデフォルト値は、新規レコードをPostしたときにサーバでセットされますが、DefaultValuesプロパティをTrueにすると、データセットのAppend/Insertメソッドで、ローカルに新規レコードを作成した際に、データディクショナリに基づいてデフォルト値がセットされます
    • DynamicDefaults: boolean:
      DefaultValues=Trueのとき、データディクショナリから取得したデフォルト値は通常セッション単位でキャッシュされますが、デフォルト値がsysdate関数を使うなど、その都度動的に変化する値の場合には、DynamicDefaults=Trueとすることで、その都度評価を行うよう指定します

整合性制約とエラーメッセージ
Oracleでは整合性制約を定義することによって、規則に合わない無効なデータを排除することができます。整合性制約には以下のような種類があります(くわしくはOracleのマニュアルなどを参照してください)

種類
意味
PRIMARY KEY 指定列の値で、表内のレコードが一意に選択できる
UNIQUE

指定列の値は、表内で一意。(NULL値は重複可能)

NOT NULL 指定列の値はNULL値を取りえない
CHECK 指定列の値の正統性を論理式でチェック

FOREIGN KEY

指定列は、他テーブルのユニークキーを参照する外部キー

アプリケーションがデータベースを更新する時(例えばデータセットがPostされるとき)に、これらの制約が破られると、Oracleは例外を発生して更新を中止し、アプリケーションに例外を通知します(トランザクションのコミット時にチェックさせる場合もあります)。この際、「ORA-00001:
一意制約
(SCOTT.PK_EMP)に反しています」のようなエラーメッセージが返りますが、このままではエンドユーザには理解不能のエラーメッセージになってしまいます。

整合性制約以外にも、更新対象レコードが他のユーザによってロックされている場合に、TOracleDataSetのLockingMode次第では「ORA-00054:
リソースビジー、NOWAITが指定されていました。」などのエラーが発生しますが、これまた、ユーザには訳のわからないメッセージです。

そのため、アプリケーションが
自前で入力値の
バリデーションを行って、ユーザにわかる形式のメッセージを表示し、ORACLEの例外を未然に防ぐという手法がよく取られていますが、ORACLEのデータベースに制約を定義したのに、同じ内容をアプリケーション・プログラムでチェックするというのでは、まさに
二度手間です。

そこで、TOracleDataSetは、ORACLEが返す制約違反のメッセージをユーザにわかりやすい形式に翻訳する手段として、OnTranslateイベントと、メッセージテーブルの二つの手段を用意しています

  • TOracleDataSet

    • EnforceConstraints: boolean:
      整合性制約(後述)のチェックを有効にします。後述のOnTranslateMessageイベントや、MessageTableの利用のためには、EnforceConstraints=Trueとする必要があります。EnforceConstraintsをTrueに設定すると、OracleDataSetはデータディクショナリ情報を読み込んで、フィールド更新の都度、制約のチェックを行うようにします。ユーザにとっては、長々と入力した最後に制約違反という空しい事態が回避されます。(データベースへのアクセスが増えるため、多少のパフォーマンス低下の代償を払うことになります。)
    • DisabledConstraints:
      EnforceConstraintsがTrueのときに、随時チェックの例外とする制約の名称を列挙します。たとえば、CachedUpdatesを使用する場合で、制約条件のチェックを随時ではなくApplyUpdates時にまとめて行いたい場合には、EnforceConstraints=Trueとしたうえで、すべての制約をDisabledConstraintsに列挙します。
    • OnTranslateMessage イベント:
      データ操作時の例外を検出し、エラーメッセージを書き換えるために使います
    • OracleDictionary.UseMessageTable: Trueにするとメッセージテーブルを使います
  • TOracleSession

    • MessageTable: string: メッセージテーブルの名前を設定します

OnTranslateMessageイベント

TOracleDataSetのデータ操作においてOracle例外が発生すると、OnTranslateMessageイベントを発生させます。このイベントハンドラには、何をしていたときに、どの制約で、どんな違反がおきたのかが、引数として与えられるので、それを参照してユーザにわかりやすいエラーメッセージを生成することが可能になります。

以下に、サンプルのOnTranslateMessagイベントハンドラを示します

  procedure TMainForm.TranslateMessage(
    Sender: TOracleDataSet;
    ErrorCode: Integer;           //ORA-NNNNN のNNNNNの値
    const ConstraintName: string; //違反の見つかった整合性制約の名称
    Action: Char;   //原因となったデータ操作の種類。I=Insert, U=Update, D=Delete, L=Lock 
  var Msg: string //エラーメッセージ(この値を書き換える)
  );
  begin
    if (ConstraintName = 'PK_EMP') then
      Msg := '従業員番号が既に登録済みです。別の番号にしてください。';
    if (ConstraintName = 'PK_DEPT') then
      Msg := '部署番号が既に登録済みです。別の番号にしてください。';
    if (ConstraintName = 'FK_DEPTNO') and (ErrorCode = 2291) then
      Msg :=
'従業員に存在しない部署番号をセットすることはできません。部署番号を確認してくき
さい。';
    if (ConstraintName = 'FK_DEPTNO') and (ErrorCode = 2292) then
      Msg := '対象の部署番号には従業員が割り当てられているため、削除・変更できません。'
+
             '先に従業員の配属を変更してください。';
  end;

制約作成上の注意

上記の例からもわかるように、アプリケーションが制約を区別するためには、
名前をつけて制約を作成する必要があります。制約を作成する際に名前を省略しないよう、プロジェクトスタンダードで統一すべきでしょう。

  たとえば、以下のように名前を付けて作成するようにしてください。
  CREATE TABLE EMP(
    EMPNO     NUMBER(4),
    ...(中略)...
    COMM      NUMBER(7,2),
    DEPTNO    NUMBER(2)    CONSTRAINT FK_DEPTNO REFERENCES DEPT
  );                                           

  このように↓名前を省略すると、エラー発生時にうまく扱えません
  CREATE TABLE EMP(
    EMPNO     NUMBER(4),
    ...(中略)...
    COMM      NUMBER(7,2),
    DEPTNO    NUMBER(2)    REFERENCES DEPT
  );                                           

メッセージテーブル

Oracleのエラーメッセージをエンドユーザ向けに変換するという機能は、上述のOnTranslateMessageイベントで完全に実現できますが、すべてのTOracleDataSetにOnTranslateMessageイベントを割り当てるのは、多数のデータセットが存在して、しかも複数のデータモジュールやフォームに分散している場合には、なかなか厄介です。また、エラーメッセージの文言を訂正する都度、アプリケーションのリコンパイルが必要になることも、プロジェクト規模が大きい場合には特に問題です。

そこで、DOAは、Oracleのデータベース上に用意されたメッセージテーブルからエラーメッセージの変換ルールを参照するように、以下の通りの手順で設定が可能です。メッセージテーブルを使えば、エラーメッセージをプロジェクト全体で共有でき、メッセージの変更はテーブルデータの更新で済むためアプリケーションのリコンパイルや再配布の必要もありません。

  1. メッセージテーブルを作成する。テーブル名は任意だが、カラムの定義は決められた形式でなければならない
  2. TOracleSession.MessageTableプロパティに、作成したメッセージテーブルの名称をセットする 3.
    それぞれのTOracleDataSetにおいて、OracleDictionary.UseMessageTable
    プロパティをTrueにセットする

メッセージテーブルの作成例を以下に示します

  create table my_messages //テーブル名は自由に決められます。カラム名は固定です
  (
    Constraint_Name varchar2(30) not null, //制約の名称
    Actions         varchar2(3)  not null, //I,U,D,L の組み合わせか、'*'(すべて)
    Parent_Child    varchar2(1)  not null, //P,C,* 
    Error_Message   varchar2(2000)         //変換後のエラーメッセージ
  );

Actionsにはメッセージを表示する原因となるデータ操作の種類を指定します。 I=Insert,
U=Update, D=Delete, L=Lock
で、この4文字を組み合わせて'IU'、'I'、'DU'のようにセットします。全部の場合は'*'を使います。

Parent_Childには、参照整合性制約違反が、マスター側(Parent)とディテール側(Child)の
どちらで起きたときにそのメッセージを表示するかを設定します。両方の場合は'*'です。以下にデータのセット例を示します

  Constr_Name  Actions  PC  Message
  PK_EMP       *        *         従業員番号が既に登録済みです
  FK_DEPT      *        C   存在しない部署番号をセットすることはできません
  FK_DEPT      D        P   対象の部署に従業員が割り当てられているため、削除できません
  FK_DEPT      U        P   対象の部署番号に従業員が割り当てられているため、変更できません