【実務・中級編】SQL ServerとWindows Formsのシームレス連携:SqlCommandBuilderを用いた高速バルクインサートとエラー時ロールバック – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

【VB.NET極意】SqlCommandBuilderは使うな。SQL Serverへの堅牢なバルクインサートと「死なない」トランザクション設計

多くのエンジニアが「楽だから」という理由で `SqlCommandBuilder` を使い、その後の保守フェーズで地獄を見る。これがWindows Forms開発の現実だ。

今日は、業務自動化の最前線に立つ諸君へ、「泥臭いが最強に速く、かつ絶対に壊れない」SQL Server連携の極意を授ける。`SqlCommandBuilder` は自動生成の便利さに目がくらむが、型推論の曖昧さや意図しないSQL生成により、大規模データではパフォーマンスが崩壊する。

我々が目指すべきは、「明示的なトランザクション」と「再試行可能な接続設計」だ。

—

1. なぜ「自動生成」を捨て、「明示的なバルク処理」を選ぶのか

`SqlDataAdapter` と `SqlCommandBuilder` を組み合わせれば、グリッドから `Update()` 一発でデータは飛ぶ。だが、ネットワークが瞬断した瞬間、アプリはフリーズし、DBには中途半端なデータが残る。

実務で求められるのは、「成功か、完全な無か」の二択だ。これを担保するためには、`SqlTransaction` を制御し、`SqlBulkCopy` またはバッチ単位の `ExecuteNonQuery` を使い分ける設計が不可欠となる。

—

2. 堅牢なバルクインサートの実装パターン

今回は、最も汎用的かつ安全な「トランザクション付き一括登録」のコードを示す。ポイントは「接続の生存期間」を最小にすることだ。

Imports System.Data.SqlClient

Public Sub ExecuteBulkInsert(dataList As List(Of MyDataModel), connectionString As String)
‘ 接続のライフサイクルは最小限に。Usingステートメントは「神」である
Using conn As New SqlConnection(connectionString)
conn.Open()

‘ トランザクションを開始し、原子性を保証する
Using trans As SqlTransaction = conn.BeginTransaction()
Try
‘ 3回のリトライロジックを組み込む
Dim retryCount As Integer = 0
Dim maxRetry As Integer = 3

While retryCount < maxRetry Try Using cmd As New SqlCommand("INSERT INTO MyTable (Col1, Col2) VALUES (@c1, @c2)", conn, trans) ' パラメータの型指定は必須。暗黙の型変換はDBのインデックスを殺す cmd.Parameters.Add("@c1", SqlDbType.NVarChar, 50) cmd.Parameters.Add("@c2", SqlDbType.Int) For Each item In dataList cmd.Parameters("@c1").Value = item.Value1 cmd.Parameters("@c2").Value = item.Value2 cmd.ExecuteNonQuery() Next End Using trans.Commit() ' 全て成功すれば確定 Exit While Catch ex As SqlException When IsTransientError(ex) ' ネットワーク一時切断(Timeout等)のみリトライ retryCount += 1 If retryCount >= maxRetry Then Throw
System.Threading.Thread.Sleep(1000 retryCount) ‘ 指数バックオフ
End Try
End While

Catch ex As Exception
‘ 何かあれば即ロールバック。これがデータ整合性の生命線
trans.Rollback()
Throw New Exception(“DB登録失敗。データ整合性は保護されました。”, ex)
End Try
End Using
End Using
End Sub

‘ 一時的なエラーか判断するヘルパー
Private Function IsTransientError(ex As SqlException) As Boolean
‘ エラーコード: 1205(デッドロック), -2(タイムアウト), 10054(接続切断)
Return ex.Number = 1205 OrElse ex.Number = -2 OrElse ex.Number = 10054
End Function

—

3. 実務で「バグを埋め込まない」ための鉄則

① SqlCommandBuilder への依存を断つ

`SqlCommandBuilder` は、テーブル構造が変わった瞬間に実行時エラーを引き起こすリスクがある。実務ツールであれば、SQLはコード内に定数として保持するか、ストアドプロシージャを呼び出すべきだ。設計をサボると、現場の誰かがテーブル定義を変えた瞬間に、君のツールはただのゴミ箱になる。

② UIスレッドをブロックしない

Windows Formsにおいて、DB処理をメインスレッドで走らせるのは「素人」の所業だ。`Async/Await` を使い、バックグラウンドで処理せよ。UIがフリーズしている間にユーザーがボタンを連打すれば、二重登録の悲劇が生まれる。

‘ UI側からの呼び出し例
Private Async Sub btnSave_Click(sender As Object, e As EventArgs) Handles btnSave.Click
btnSave.Enabled = False ‘ 二重クリック防止
Try
Await Task.Run(Sub() ExecuteBulkInsert(myData, connStr))
MessageBox.Show(“完了しました”)
Catch ex As Exception
MessageBox.Show(ex.Message)
Finally
btnSave.Enabled = True
End Try
End Sub

③ ネットワークエラーは「想定内」

VPN環境や不安定なWi-Fi環境で業務アプリを動かすなら、エラーは「例外」ではなく「日常」だ。`IsTransientError` のような判定ロジックを必ず挟み、即座に死なせず、静かに再試行する。この「気遣い」が、君をただのプログラマーから「アーキテクト」へと昇華させる。

—

最後に:なぜVB.NETなのか

VB.NETは古いと言われる。しかし、Windowsの深いレイヤーへのアクセスと、Visual Studioによる開発生産性の高さは、今なお中小規模の業務システムにおいては最強の武器だ。

ツールを作って終わりではない。「誰もが直感的に使え、ネットワークが途切れても不機嫌にならない。そんな堅牢なシステムを、君の手で作り上げろ。」

コードを書くことは、ユーザーの業務を自動化するだけではない。彼らの「不安」をコードの整合性で消し去ることなのだ。さあ、IDEを開け。今日の設計は、昨日よりも強固なはずだ。

タイトルとURLをコピーしました