この特集のトップページへ
>
Chapter 6:ビジネスロジックの設計
6.2.2 顧客情報の編集
●データオブジェクトの構築
以上でデータオブジェクトであるDataObj.Customerコンポーネントを通じて,既存の顧客情報の取得ならびに変更ができるようになったが,Chapter3で説明したように,今回のサンプルではデータが更新されたときには,その履歴をすべて履歴テーブルに記録するような機構を設けることにする。
DataObj.Customerコンポーネントにおいて,データを更新するのはList 6-17に示したUpdateRecordメソッドとList 6-18に示したUpdateRecord_BILLDAYメソッドである。この2つのメソッドに対し,履歴を追加する機構を備えるようプログラムを拡張してゆく。
履歴テーブルは,Chapter3においてTable 6-6のように定義した。
Table 6-6 履歴テーブル
| フィールド名 | 型 | サイズ | Null | 解説 |
| ID | 数値型(オートナンバー) | − | 不可 | レコードに対して唯一無二の値を割り当てる |
| DATE | 日付型 | − | 不可 | 変更が生じた日時 |
| TABLENAME | 文字型 | 32 | 不可 | 変更が生じたデータベーステーブル名 |
| FIELDNAME | 文字型 | 32 | 不可 | 変更が生じたデータベーステーブルのフィールド名 |
RECORDID |
数値型 |
− |
不可 |
変更が生じたレコードのレコードID(そのデータベーステーブルにおいて主キーとなっているフィールドの値) |
| OLDDATA | 文字型 | 256 | 可 | 更新前のデータ |
| NEWDATA | 文字型 | 256 | 可 | 更新後のデータ |
| UPDATEUSER | 文字型 | 256 | 不可 | 更新したユーザーのアカウント名 |
UpdateRecordメソッドとUpdateRecord_BILLDAYメソッドに対し,直接履歴テーブルも書き加えるようにプログラムを変更してもかまわないが,履歴テーブルを書き換えるという操作は,のちに実装してゆく製品情報テーブルや伝票テーブルの更新などにも必要となってくる。そこで,履歴テーブルを操作するDataObj.Historyというコンポーネントを新たに導入し,履歴テーブルを操作するメソッドをそこにまとめてしまうことにしよう。
DataObj.Historyコンポーネントを作るためには,まずDataObjプロジェクトにおいてHistoryクラスを追加する。そして,Historyクラスのトランザクションの状態を「トランザクションが必要」にするため,HistoryクラスのMTSTransactionModeプロパティの値を[RequiresTransaction]に変更する(Fig.6-41)。
Fig.6-41 Historyクラスのプロパティ

クラスモジュールの設定が終わったならば,作成したHistoryクラスにList 6-20に示すプログラムを記述する。
List 6-20の29行目で記したAddHistoryメソッドは,データベーステーブル名,フィールド名,更新されたレコードのレコードID,更新前の値,更新後の値を与えると,それを履歴テーブルに追加する。29行目よりまえの部分は,コンストラクタ文字列を取得してそれをデータベース接続文字列として内部的に利用するための処理である(「データベース接続文字列を動的に設定できるようにする」を参照)。
DataObj.Historyコンポーネントを実装したならば,List 6-17のUpdateRecordメソッドとList 6-18のUpdateRecord_BILLDAYメソッドをそれぞれList 6-21,List 6-22に示すように変更する。
List 6-21とList 6-22に追加された処理は,「DataObj.Historyコンポーネントを実体化しておき,データを設定するまえの値と設定しようとしている値が違った場合には,そのAddHistoryメソッドを呼び出して履歴テーブルに追加する」というものである。Fig.6-41に示したように,DataObj.Historyコンポーネントは[トランザクションが必要]に設定されているため,DataObj.Customerオブジェクトから実体化したときには,同じトランザクション範囲内に属することになる。したがって,DataObj.HistoryコンポーネントのAddHistoryメソッドの処理に失敗したら,トランザクションはアボートとなる。つまり,履歴テーブルへの書き込みに失敗したにもかかわらず,顧客情報テーブルが書き換えられてしまうことはない。
List 6-21の26行目とList 6-22の20行目では,DataObj.Historyコンポーネントを実体化する際にCreateObject文を使っている。これを,Newキーワードに置き換えてはならない。List 6-21ならびにList 6-22は,DataObj.Customerコンポーネントに実装するものである。つまり,DataObj.CustomerコンポーネントもDataObj.Historyコンポーネントも同じDataObjという名前のプロジェクトに備わるクラスモジュールに記述するわけだ。Visual Basicにおいて,同一のプロジェクトに備わるクラスモジュールをNewキーワードで実体化したときには,COMやCOM+の機構を使って実体化されるのではなく,Visual Basic内部で実体化される。つまり,COM+のアクティベータを通らないことになり,インタセプタなどは設定されないことになる。その結果,実体化されたオブジェクト(この実体化されたオブジェクトはCOMやCOM+の機構を通っていないので,COMオブジェクトではなく,単なるVisual Basicのオブジェクトである)は,同じトランザクション範囲に入らなくなる。
List 6-21とList 6-22では,設定するまえの値と設定しようとしている値が異なるかどうかを調べるのに,あまり一般的に使われないXor演算子を使っている。ここで,その点について補足説明をしておく。
たとえば,List 6-21の50〜53行目は次のようにした。
50: If objRec.Fields("NAME").Value <> NAME Or _
(IsNull(objRec.Fields("NAME").Value) Xor IsNull(NAME)) Then
51: objHistory.AddHistory "顧客情報", "NAME", _
CUSTOMERID, objRec.Fields("NAME").Value, NAME
52: objRec.Fields("NAME").Value = NAME
53: End If
50行目は,objFields("NAME").ValueとNAME変数の内容が等しくないことを確認している文である。仮に,50行目を次のようにしたとしよう。
50: If objRec.Fields("NAME").Value <> NAME Then
こうした場合,objRec.Fields("NAME").ValueもしくはNAME変数の内容がNullであると,“objRec.Fields("NAME").Value <> NAME”の条件式の評価はTrueにもFalseにもならず,Nullとなる。つまり,objRec.Fields("NAME").ValueもしくはNAME変数の内容がNullの場合,51行目と52行目は実行されないことになる。そこで次のように,Xor演算子とIsNull関数を使っているのだ。
50: If objRec.Fields("NAME").Value <> NAME Or _
(IsNull(objRec.Fields("NAME").Value) Xor IsNull(NAME)) Then
ただし,Table 6-4に示した顧客情報テーブルを見るとわかるように,NAMEフィールドはNull値を不許可にしてある。よって,objRec.Fields("NAME").ValueもNAME変数もNullになることはないため(NAME変数がNullである場合にはList 6-21の33行目にあるChk_NAMEメソッドの呼び出しで実行時エラーが発生する(List 6-2を参照),NAMEフィールドに限っては,上記のOr演算子以降は不要である。しかし,ここではほかのフィールドとの表記の統一化のためにOr以降の条件を付与した。
Xor演算子は,演算対象のどちらかがTrueであればTrueとなる演算子である(Table 6-7)。
Table 6-7 Xor演算子
| A | B | A Xor B |
| True | True | False |
| True | False | True |
| False | True | True |
| False | False | False |
そしてIsNull関数は,引数に与えられた値がNullであればTrueを,そうでなければFalseを返す関数である。よって,“IsNull(objRec.Fields("NAME").Value) Xor IsNull(NAME)”の部分は,objRec.Fields("NAME").ValueがNullであるかNAME変数の内容がNullであれば,全体としてTrueとなる条件式である。つまり,条件式“objRec.Fields("NAME").Value<> NAME Or (IsNull(objRec.Fields("NAME").Value) Xor IsNull(NAME))”は,次のいずれかの条件のときにTrueとなる。
(1)objRec.Fields("NAME").ValueとNAME変数の内容がともにNullでなく,かつobjRec.Fields("NAME").ValueとNAME変数の内容が異なるとき
(2)objRec.Fields("NAME").ValueとNAME変数の内容のいずれかがNullのとき
(2)の条件は少々わかりにくいが,データベースに元から格納されている値(objRec.Fields("NAME").Value)がNullで,データベースに格納しようとしている値(NAME)がNull以外,もしくは,その逆という条件を示している。つまり,レコードのフィールド値がNullのものをNullでないものに変更しようとしている場合か,NullでないものをNullに変更しようとしている場合の条件式を示す。もし,条件式“objRec.Fields("NAME").Value<> NAME Or (IsNull(objRec.Fields("NAME").Value) Xor IsNull(NAME))”のOr以降の部分を省略してしまうと,(2)の条件により,この条件式がTrueになることはない。すなわち,NullのものをNullでないものに変更しようとしたり,NullでないものをNullに変更しようとしている場合には,51行目にある履歴の追加と,52行目にあるレコードの値の変更は,実行されなくなってしまう。
データベーステーブルのフィールドを扱う場合には,Null値を扱うことが比較的多い。そのため,条件式を使う際にNull値の判断を適切に処理しないと,思わぬ結果を招くことになるので注意したい。
Or演算子は,条件式のいずれかがTrueであればTrueを返す演算式である。たとえば,objRec.Fields("NAME").ValueがNullでNAME変数の内容がNullでないとしよう。このとき,条件式“objRec.Fields("NAME").Value<> NAME Or (IsNull(objRec.Fields("NAME").Value) Xor IsNull(NAME))”について考えてみよう。まず,先頭の式“objRec.Fields("NAME").Value <> NAME)”はobjRec.Fields("NAME").ValueがNullであるため,評価後の結果はNullとなる。そして,“IsNull(objRec.Fields("NAME").Value) Xor IsNull(NAME))”は,objRec.Fields("NAME").ValueがNullであり,NAME変数の内容がNullでないため,全体としてTrueになる。よって,“objRec.Fields("NAME").Value<> NAME Or (IsNull(objRec.Fields("NAME").Value) Xor IsNull(NAME))”は,“Null Or True”となる。この式はNullを含むことになるのだが,Or演算子は条件式のいずれかがTrueであれば,もう片方がたとえNullであってもTrueを返すため,全体としてはTrueとなる。このようにOr演算子は,演算内容のどこかにNullが含まれていたとしても,全体としてNull値になることはない。ただし,片方がFalseで片方がNullの場合,もしくは,両者がNullの場合には,全体としてNullになる。詳しくは,Visual Basicのオンラインヘルプの「Or演算子」「And演算子」「Xor演算子」などの項目を参照してほしい。
| Chapter 6 21/92 |
