第7回 マスタ/詳細テーブルにおける更新処理:連載:Visual Studio 2005によるWindowsデータベース・プログラミング(4/4 ページ)
外部キー制約や自動インクリメント列を使いこなして、追加・更新・削除されたマスタ/詳細データをDBに反映する。
リレーションシップにおける連鎖更新の必要性
以上の修正により、[データの保存]ボタンをクリックすれば正しくデータベースを更新できるのですが、追加処理に関しては押さえておくべき重要なポイントが隠されています。そのカギとなるのが、今回の冒頭の[リレーションシップ]ダイアログで設定しておいた連鎖更新です。
連鎖更新の必要性を説明する前に、それが必要となるそもそもの要因である「自動インクリメント列」について説明しておきます。
■自動インクリメント列について
注文テーブルの主キーであるOrderID列は、自動インクリメント列として設定されています。具体的にはOrderID列のプロパティを見ると、[IDENTITYの指定]が「はい」になっています。
図8 注文テーブル(Ordersテーブル)のOrderID列のIDENTITY指定
データベース・エクスプローラでOrdersテーブルを開いたところ。[IDENTITYインクリメント]が「1」、[IDENTITYシード]が「1」に設定されているため、レコードを追加するたびにOrderID列の値は1、2、3、……と自動的に割り振られていく。
(1)IDENTITYの指定。
この設定により、注文テーブルにレコードを挿入したときには、ほかのレコードのOrderID番号と重複しないユニークなOrderID番号が、OrderID列に自動的に設定されます。自動インクリメント列により、主キーの一意性が自動的に確保されるわけです。
もしこの機能がなければ、開発者は「OrderID番号管理テーブル」を作成したり、GUID番号を利用したりするなどして、自分でユニークなOrderID番号を管理/作成しなくてはなりません。
そして、自動インクリメント列の機能はデータテーブルにも実装されています。[データテーブル]ウィンドウからテーブルをフォームにドラッグ&ドロップしてデータテーブルを作成した場合には、元のテーブルに合わせて自動的に自動インクリメント列が設定されます。
図9 注文データテーブルのOrderID列のAutoIncrement設定
データセット・デザイナでデータセットを開いたところ。注文データテーブルのOrderID列のプロパティを見ると、列のAutoIncrementプロパティが「True」になっていて自動インクリメント列となっているのが分かる。
(1)AutoIncrementプロパティ。
■データテーブルのレコードがテーブルに追加されるまで
さて、開発者が楽できる自動インクリメント列の機能ですが、本連載のようにデータテーブルを活用している場合には、データテーブルとデータベースの両方で自動インクリメント列が設定されることになり、動作が少々複雑になります。
というのも、データテーブルで自動インクリメント列により割り当てられた番号は、データベースのテーブルで自動インクリメント列により割り当てられた番号とはまったくの無関係であるためです。データテーブルに対する操作は非接続(つまりはオフライン)で行っているわけですから、これは当然ですね。
このため以下のような処理が必要になります。ここでは注文データテーブルの新規レコードとその子となっている注文明細データテーブルの新規レコードをデータベースに反映させる場合について考えています。
まず、注文データテーブルにレコードを追加すると、注文データテーブル内でユニークなOrderID番号が割り振られます。しかし、それはレコードを注文テーブルに追加したときに振られるOrderID番号とは異なるものであり、後者が最終的な本当のOrderID番号です。
そうなると続いては、確定した本当のOrderID番号を注文データテーブルのレコードにも反映させておかなければなりません。そしてさらには、そのOrderID番号を注文明細データテーブルのレコードにも反映させる必要があります。ここまで行って、やっと注文明細データテーブルのレコードを注文明細テーブルに追加することができます。
以上の処理が実際にはどのようにして行われるのか、図を使いながら説明していきましょう。
まず、ユーザーにより注文データが新規作成され、続いて注文明細データが新規作成されます。このとき注文データテーブルでは、自動インクリメント列によりOrderID番号=20000が割り振られたとします。グリッド上ではDataGridViewコントロールにより、自動的にこの20000が注文明細データテーブルのOrderID列にも設定されます。
次に、ユーザーは[データの保存]ボタンをクリックします。するとUpdateメソッドにより、まず注文データテーブルのレコードが注文テーブルに追加されます。そして注文テーブルの自動インクリメント列により、OrderID番号=10251が注文テーブルに格納されたレコードに割り振られたとしましょう。
実はこの後、続けて注文テーブルから注文データテーブルへレコードの再読み込みが行われます。
これがどのようにして行われるのかは、先ほど図7のところで触れた注文テーブルアダプタのInsertCommandプロパティの値をよく見ると分かります。このプロパティに設定されているSQL文は実はINSERT文だけではなく、実際にはSELECT文が付加された次のようなバッチ・クエリになっています。
INSERT INTO [dbo].[Orders] ([CustomerID], [EmployeeID], ……)
VALUES (@CustomerID, @EmployeeID, ……);
SELECT OrderID, CustomerID, ……
FROM Orders WHERE (OrderID = SCOPE_IDENTITY())
INSERT文だけでなく、続けてSELECT文が実行されるバッチ・クエリとなっている。最後の行の「SCOPE_IDENTITY()」は、直前に使用された自動インクリメント列の番号を取得するためのSQL Serverの関数である。
つまりレコードを追加するだけでなく、いま追加した行をすぐさま取得しています。そしてこのようにして取得された新しいレコードは、テーブルアダプタにより注文データテーブルに反映されます。これにより、注文データテーブルのOrderID番号が最終的な値に変更されるわけです。
さて、ここでやっと連鎖更新の出番です。注文データテーブルのレコードのOrderID番号更新は、連鎖更新により注文明細データテーブルのレコードのOrderID番号を20000→10251に更新します。
以上の図10から図13までの処理はすべて連続して行われます。後は、注文明細テーブルアダプタのUpdateメソッドにより、注文明細データテーブルの追加されたレコードを注文明細テーブルに追加するだけです。
以上でデータセットからデータベースへの更新は完了しました。しかし実はまだ1点、考慮すべき問題点が残っています。最後にこれについて説明しておきます。
■AutoIncrementSeedプロパティとAutoIncrementStepプロパティは「-1」に
その問題点とは、先ほどの図12において、注文テーブルで実際に付与されたOrderID番号を持ったレコードが注文データテーブルに書き戻される際、そのOrderID番号を持ったレコードが注文データテーブルにすでに存在している場合です。
極端な話、注文データテーブルにOrderID番号=20000とOrderID番号=20001のレコードが存在しており、OrderID番号=20000の方のレコードをまずデータベースに追加したら、データベースで付与されたOrderID番号が20001だったという場合には、それを注文データテーブルに書き戻すことができません。
このような事態を回避するために、注文データテーブルの自動インクリメント列で発行される番号が、注文テーブルのそれと決して重複しないようにあらかじめ設定しておきます。
具体的には、注文データテーブルのOrderID列(図9参照)で、AutoIncrementSeedプロパティとAutoIncrementStepプロパティの値を「-1」に設定します。これにより注文データテーブルでのOrderID番号は-1、-2、-3、……と振られていきます。注文テーブルで振られるOrderID番号は1、2、3、……ですから重複することはなくなります*7。
*7 筆者はこのテクニックを日経BPソフトプレス社から発行されている書籍『プログラミングMicrosoft ADO.NET 2.0』で知りました。この本は960ページとボリュームがありますが非常に優れた内容ですので、.NETのデータベース・プログラミングに携わっている方にはオススメの1冊です。
以上、今回はマスタ/詳細テーブルにおける更新処理について見てきました。いろいろな手を使いながら制約違反を回避しつつ(整合性を保ちつつ)、何とかテーブルを更新できるようになりました。しかし2つのテーブルを扱う場合には、まだ重要な仕事が残っています。そう、トランザクション処理です。次回ではこれについて解説する予定です。
Copyright© Digital Advantage Corp. All Rights Reserved.