金宏 和實(かねひろ かずみ)

 富山県高岡市 株式会社イーザー副社長。今年も残すところ半月あまりになりましたね。本当に一年が経つのは速いですね。読者のみなさんにとって今年はどんな年だったでしょうか。私はどうもまだ締めくくることができないので,残り少ない日数ですが,もう少し粘ってみたいと思っています。いずれにしても,みなさん良いお年をお迎えください!

 第8回第9回とロボットのライントレース競技会に参加するチームの情報と競技の結果をSQL Server 2005上のデータベースに保存する処理を作成してきました。

 Visual Studio 2005では,特にオプションをインストールしなくてもSQL Server 2005のテーブル作成やテーブルのデータを表示することができます。アプリケーション開発の統合環境として,必要なものは最初から揃っている。そんなところがVisual Studio 2005のEclipseなどに対するアドバンテージではないかと筆者は思います。

 さて前回は,データソースの生成時に自動作成されたクラスを利用すると,データベース操作を短いコードで実現できるというメリットを紹介しました。具体的には,データソースの追加でDataSetを作成し,TableAdapterクラスを使ってDataTableのデータを取得してレコードの追加/削除などの変更を行い,TableAdapterで更新内容を物理データベースに反映させるという一連の処理が簡単に書けるのでした(図1)。DataSetは,物理データベースの一部をコピーしたインメモリーなデータベースであることをおわわかりいただけたはずです。

図1●データベース操作の各クラスと物理データベースの関係
図1●データベース操作の各クラスと物理データベースの関係

 このように,ある程度まとまったデータをローカル側で変更してから,物理データベースに書き戻すという方法の問題点は,複数のユーザーからの同時更新が頻繁に発生するようなケースでは更新の競合が発生してしまうことです。あるユーザーがDataSetを取得し,ローカルで編集している間に,別のユーザーが同じデータを含むDataSetを取得し,ローカルで編集する。そして,どちらかが先に物理データベースに変更を保存したあとで,もう一方が変更を保存すると,先に保存した人の変更内容が失われてしまいます。

 もちろん,そんな場合は同時更新の例外が発生するので,後で更新しようとした人の変更を保存しないようにすることも可能ですが,誰かの作業は無駄になってしまいます。

 対策としては,例えば,誰かがローカルに取得して編集中のデータを別のユーザーが取得することができないようにチェックするという方法が考えられます。でも,競合を避ける基本は,一人のユーザーが一度に編集できるデータを小さな単位にすることです。

 そこで今回は,ライントレース競技会の結果を記録するプログラムについて,Teamsテーブル(表1)から1チームのデータ,つまり1レコードを取得し,編集を加えたのちに更新するという処理を中心に解説します。

表1●チームテーブル(テーブル名:Teams,主キー:NO)
名前 列名 データ型 NULLを許容
NO TeamNo int -
チーム名 TeamName nvarchar(20) -
代表者 Represent nvarchar(10) -
電話番号 Telno nchar(13)
E-Mail Email varchar(50)
備考 Note nvarchar(30)
出走順1 Order1 int -
タイム1 Time1 decimal(6,2) -
ペナルティ1 Penalty1 decimal(6,2) -
出走順2 Order2 int -
タイム2 Time2 decimal(6,2) -
ペナルティ2 Penalty2 decimal(6,2) -

SQL文の実行にはSqlCommandクラスを使う

 一つ一つのチームの情報を表示,編集できるフォームを作成します。詳細は省略しますが,図2が実行時のイメージです。

図2●1チームの情報を表示/編集するフォーム
図2●1チームの情報を表示/編集するフォーム
[画像のクリックで拡大表示]

 フォームとデータベース・アクセスの関係は図3のようになります。まず,Form_Loadイベントでデータベースとの接続を確立します(リスト1)。

図3●フォームとSqlCommandの関係
図3●フォームとSqlCommandの関係

Private Sub Form5_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
  cn = New SqlConnection("Data Source=(local)\SQLEXPRESS;" + _
  "Initial Catalog=RaceDB;Integrated Security = True;Pooling=False")
  Try
    cn.Open()
  Catch ex As SqlException
    MessageBox.Show(ex.Message)
    Exit Sub
  Catch ex As Exception
    MessageBox.Show(ex.Message)
    Exit Sub
  End Try
リスト1●Form_Loadプロシジャでデータベース接続を確立

 SqlConnectionクラスのオブジェクトcnに接続文字列を設定し,cn.Open()で接続を開きます。ここではローカルなサーバーに接続しています。接続文字列が間違っていたり,SQL Serverのサービスが起動していない場合はSqlExceptionの例外が発生します。

 オブジェクトcnはフォーム全体で使えるように,クラスの先頭でPrivate宣言しています(リスト2)。

Imports System.Data.SqlClient
Imports System.Text.RegularExpressions

Public Class Form5
  Private cn As SqlConnection
  Private Enum UpdateButtonMode As Integer
    register
    update
  End Enum
  Dim btnMode As UpdateButtonMode
リスト2●フォームの先頭のコード。SqlConnectionなどが短い名前で使えるようにSystem.Data.SqlClient名前空間をインポートしている

 txtNo_Leaveイベント(テキストボックスtxtNoにチームのNOを入力後,txtNoから離れるときに発生)で,txtNo.Textを抽出条件としてSELECT文を発行してレコードの存在を確かめます(リスト3)。

Private Sub txtNo_Leave(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles txtNo.Leave

  (中略,ここでは入力値のチェックを行います)

  Dim cmd As SqlCommand
  Dim sql As String
  Dim dr As SqlDataReader
  sql = "SELECT * from Teams where TeamNo = " + txtNo.Text
  cmd = New SqlCommand(sql, cn)
  dr = cmd.ExecuteReader()

  If dr.Read = False Then
    btnMode = UpdateButtonMode.register
    lblMode.Text = "新規"
    btnDelete.Text = "取消"
  Else
    btnMode = UpdateButtonMode.update
    lblMode.Text = "修正"
    btnDelete.Text = "削除"
    txtTeamName.Text = dr.Item("TeamName")
    txtRepresent.Text = dr.Item("Represent")
    txtTelNo.Text = Trim(dr.Item("TelNo") & "")
    txtEmail.Text = dr.Item("Email") & ""
    txtNote.Text = dr.Item("Note") & ""
    txtOrder1.Text = dr.Item("Order1")
    txtTime1.Text = dr.Item("Time1")
    txtPena1.Text = dr.Item("Penalty1")
    txtOrder2.Text = dr.Item("Order2")
    txtTime2.Text = dr.Item("Time2")
    txtPena2.Text = dr.Item("Penalty2")
    lblResult1.Text = dr.Item("Time1") + dr.Item("Penalty1")
    lblResult2.Text = dr.Item("Time2") + dr.Item("Penalty2")
  End If
  txtNo.Enabled = False
  dr.Close()
End Sub
リスト3●txtNo.Textを抽出条件としてSELECT文を発行するtxtNo_Leaveプロシジャ

 SQL文を発行するにはSqlCommandクラスを使用します。 cmd = New SqlCommand(sql, cn)でSELECT文と接続オブジェクトcnをパラメータとして渡し,SqlCommandクラスのオブジェクトcmdを作成しています。