|
第8回,第9回とロボットのライントレース競技会に参加するチームの情報と競技の結果をSQL Server 2005上のデータベースに保存する処理を作成してきました。
Visual Studio 2005では,特にオプションをインストールしなくてもSQL Server 2005のテーブル作成やテーブルのデータを表示することができます。アプリケーション開発の統合環境として,必要なものは最初から揃っている。そんなところがVisual Studio 2005のEclipseなどに対するアドバンテージではないかと筆者は思います。
さて前回は,データソースの生成時に自動作成されたクラスを利用すると,データベース操作を短いコードで実現できるというメリットを紹介しました。具体的には,データソースの追加でDataSetを作成し,TableAdapterクラスを使ってDataTableのデータを取得してレコードの追加/削除などの変更を行い,TableAdapterで更新内容を物理データベースに反映させるという一連の処理が簡単に書けるのでした(図1)。DataSetは,物理データベースの一部をコピーしたインメモリーなデータベースであることをおわわかりいただけたはずです。
図1●データベース操作の各クラスと物理データベースの関係 |
このように,ある程度まとまったデータをローカル側で変更してから,物理データベースに書き戻すという方法の問題点は,複数のユーザーからの同時更新が頻繁に発生するようなケースでは更新の競合が発生してしまうことです。あるユーザーがDataSetを取得し,ローカルで編集している間に,別のユーザーが同じデータを含むDataSetを取得し,ローカルで編集する。そして,どちらかが先に物理データベースに変更を保存したあとで,もう一方が変更を保存すると,先に保存した人の変更内容が失われてしまいます。
もちろん,そんな場合は同時更新の例外が発生するので,後で更新しようとした人の変更を保存しないようにすることも可能ですが,誰かの作業は無駄になってしまいます。
対策としては,例えば,誰かがローカルに取得して編集中のデータを別のユーザーが取得することができないようにチェックするという方法が考えられます。でも,競合を避ける基本は,一人のユーザーが一度に編集できるデータを小さな単位にすることです。
そこで今回は,ライントレース競技会の結果を記録するプログラムについて,Teamsテーブル(表1)から1チームのデータ,つまり1レコードを取得し,編集を加えたのちに更新するという処理を中心に解説します。
表1●チームテーブル(テーブル名:Teams,主キー:NO)名前 | 列名 | データ型 | NULLを許容 |
---|---|---|---|
NO | TeamNo | int | - |
チーム名 | TeamName | nvarchar(20) | - |
代表者 | Represent | nvarchar(10) | - |
電話番号 | Telno | nchar(13) | ○ |
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チームの情報を表示/編集するフォーム [画像のクリックで拡大表示] |
フォームとデータベース・アクセスの関係は図3のようになります。まず,Form_Loadイベントでデータベースとの接続を確立します(リスト1)。
図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
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
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
SQL文を発行するにはSqlCommandクラスを使用します。 cmd = New SqlCommand(sql, cn)
でSELECT文と接続オブジェクトcnをパラメータとして渡し,SqlCommandクラスのオブジェクトcmdを作成しています。