第7回 マスタ/詳細テーブルにおける更新処理連載:Visual Studio 2005によるWindowsデータベース・プログラミング(3/4 ページ)

» 2007年11月27日 00時00分 公開
[遠藤孝信デジタルアドバンテージ]

注文テーブルと注文明細テーブルの更新

 今回では、前回で作成したプログラムをベースに作業していますので、現時点ではツールバーの[データの保存]ボタンをクリックしても、注文データテーブルしか保存されません(前回で行ったように、マスタ/詳細テーブルをフォームにドラッグ&ドロップするだけで参照画面については自動作成できたわけですが、さすがに更新処理までは自動作成されないということです)。

 [データの保存]ボタンのイベント・ハンドラを見ると、次のようになっているはずです。このコードは、前回で[データソース]ウィンドウから注文テーブルの項目をフォームにドラッグ&ドロップしたときに自動作成されたコードです。

Private Sub 注文BindingNavigatorSaveItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles 注文BindingNavigatorSaveItem.Click

  Me.Validate()
  Me.注文BindingSource.EndEdit()
  Me.注文TableAdapter.Update(Me.NORTHWNDDataSet.注文)

End Sub

[データの保存]ボタンのイベント・ハンドラ(自動作成されたコード)

 ここに次のような2行を追加するだけで済めば楽なのですが、実際にはデータベース内の注文テーブルと注文明細テーブルのリレーションシップに設定された外部キー制約により、そう簡単にはいきません。

Me.注文明細BindingSource.EndEdit()
Me.注文明細TableAdapter.Update(Me.NORTHWNDDataSet.注文明細)


 単純にこれを行ってしまうと、先ほどデータテーブルで起きたような問題が今度はデータベースのテーブルで起こってしまいます。具体的には、子テーブルにレコードがあるのに親テーブルにはそれに対応したレコードがないといった状況です。

 このため、1度のUpdateメソッド呼び出しでテーブルを更新するのではなく、追加されたレコード、更新されたレコード、削除されたレコードと場合分けして、Updateメソッドを呼び出していく必要があります。

■テーブルアダプタのUpdateメソッド

 その前に、テーブルアダプタに用意されているUpdateメソッドの仕組みについて簡単に解説しておきます。

 ユーザーの操作により行われたレコードの追加、削除、変更は、データ連結によりデータテーブルに反映されます。このとき、追加、削除、変更されたデータテーブルのレコードには、その操作を示すマークが付けられます。

 Updateメソッドは、このようにしてマークが付けられたレコードを順に実際のテーブルに反映していきます。このとき、例えば「追加マーク」の付いたレコードについては、テーブルアダプタのInsertCommandプロパティに設定されたSQL文(INSERT文)を使用してテーブルに追加します。更新、削除についても同様で、UpdateCommandプロパティやDeleteCommandプロパティに設定されているSQL文を使用してテーブルのレコードを更新、削除します。

図7 テーブルアダプタのInsertCommandプロパティ
テーブルアダプタのUpdateメソッドがレコードを追加するときには、このプロパティに設定されたSQL文が実行される。
  (1)InsertCommandプロパティ。

 なお、「削除マーク」の存在からも分かるように、データテーブル上でレコードを削除してもそのレコードに削除マークが付けられるだけで、実際にはすぐには削除されません。このようにしておかないと、Updateメソッドが呼び出されたときにどのレコードを削除すればよいのかが分からないためです*5

*5 データベースは複数のユーザーによりアクセスされる可能性があるので、データベースには存在するがデータテーブルには存在しないレコードを削除するといったことはできません。


■削除処理は子テーブルから

 さて、データテーブルのレコードには追加、更新、削除のマークが付いていますから、これを利用すれば、それぞれの種類のレコードについて個別に更新処理が行えます。ここではまず、削除マークの付いたレコードの削除処理からプログラミングしていきます。

 削除処理では、親テーブルのレコードを先に削除してしまうと、子テーブルには親のないレコードが残ってしまいますので、まず子テーブルのレコードから削除しなければなりません。

 具体的には、最初に注文明細データテーブルから削除マークの付いたレコードのみを集め、Updateメソッドによりそれらのレコードをデータベースに反映します(これによりデータベースからレコードが実際に削除されます)。続いて、注文データテーブルに対しても同様の処理を行います。これらの処理は次のように記述できます。

Dim deletedRows() As DataRow

' 注文明細テーブルの削除処理
deletedRows = Me.NORTHWNDDataSet.注文明細.Select( _
                                "", "", DataViewRowState.Deleted)
If deletedRows.Length <> 0 Then
    Me.注文明細TableAdapter.Update(deletedRows)
End If

' 注文テーブルの削除処理
deletedRows = Me.NORTHWNDDataSet.注文.Select( _
                                "", "", DataViewRowState.Deleted)
If deletedRows.Length <> 0 Then
    Me.注文TableAdapter.Update(deletedRows)
End If

削除処理(削除されたレコードの更新)

「DataViewRowState.Deleted」はデータテーブルの削除されたレコードを示す。


 ここでの最初のポイントは、データテーブル(このコード内では「注文明細」と「注文」)のSelectメソッド呼び出しです。

 Selectメソッドは通常、データテーブルから特定条件のレコードだけを抽出したり、レコードを並べ替えたりするために利用されますが、第3パラメータにDataViewRowState列挙体の値を指定することにより、追加、更新、削除としてマークされたレコードをDataRowオブジェクトの配列として取得することができます。DataRowオブジェクトとは、データテーブル内のレコードを示すオブジェクトです。

 そしてもう1つのポイントは、UpdateメソッドがパラメータとしてDataRowオブジェクトの配列を受け取ることができるという点です。これにより、指定したレコードのみをデータベースに反映させることができるというわけです。

■追加処理は親テーブルから

 削除処理とは逆に、追加処理ではまず親テーブルのレコードを追加してから、子テーブルのレコードを追加する必要があります。コードは次のようになります。

Dim addedRows() As DataRow

' 注文テーブルの追加処理
addedRows = Me.NORTHWNDDataSet.注文.Select( _
                                "", "", DataViewRowState.Added)
If addedRows.Length <> 0 Then
    Me.注文TableAdapter.Update(addedRows)
End If

' 注文明細テーブルの追加処理
addedRows = Me.NORTHWNDDataSet.注文明細.Select( _
                                "", "", DataViewRowState.Added)
If addedRows.Length <> 0 Then
    Me.注文明細TableAdapter.Update(addedRows)
End If

追加処理(追加されたレコードの更新)
「DataViewRowState.Added」はデータテーブルに追加されたレコードを示す。

 更新処理については、今回のサンプル・プログラムではユーザーが主キーや外部キーを変更することはないため*6、更新は親テーブルからでも子テーブルからでも構いません。今回の場合では、上記の追加処理で、

DataViewRowState.Added

の部分を、

DataViewRowState.Added Or DataViewRowState.ModifiedCurrent

と変更することにより、追加分と変更分の処理をまとめてしまうことができます。

*6 アプリケーションの画面上では変更することができてしまいますが、これは単なる手抜きです。実際には、テキストボックスやグリッドの列を読み取り専用とするか、場合によっては画面に表示する必要がないかもしれません。


 以上をまとめると、[データの保存]ボタンが押されたときの処理(注文BindingNavigatorSaveItem_Clickメソッドの内容)は次のようになります。

Private Sub 注文BindingNavigatorSaveItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles 注文BindingNavigatorSaveItem.Click

  Me.Validate()
  Me.注文BindingSource.EndEdit()
  Me.注文明細BindingSource.EndEdit()

  Dim rows() As DataRow

  ' 注文明細テーブルの削除処理
  rows = Me.NORTHWNDDataSet.注文明細.Select( _
                  "", "", DataViewRowState.Deleted)
  If rows.Length <> 0 Then
    Me.注文明細TableAdapter.Update(rows)
  End If

  ' 注文テーブルの削除処理
  rows = Me.NORTHWNDDataSet.注文.Select( _
                  "", "", DataViewRowState.Deleted)
  If rows.Length <> 0 Then
    Me.注文TableAdapter.Update(rows)
  End If

  ' 注文テーブルの追加・変更処理
  rows = Me.NORTHWNDDataSet.注文.Select("", "", _
    DataViewRowState.Added Or DataViewRowState.ModifiedCurrent)
  If rows.Length <> 0 Then
    Me.注文TableAdapter.Update(rows)
  End If

  ' 注文明細テーブルの追加・変更処理
  rows = Me.NORTHWNDDataSet.注文明細.Select("", "", _
    DataViewRowState.Added Or DataViewRowState.ModifiedCurrent)
  If rows.Length <> 0 Then
    Me.注文明細TableAdapter.Update(rows)
  End If

End Sub

完成した[データの保存]ボタンのイベント・ハンドラ

 続いては、リレーションシップにおける連鎖更新の必要性について説明していきます。

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。