DataGridViewにデータバインドして編集・削除するのに最適な方法は?
WindowsFormで業務アプリを開発する場合、DataGridViewの様にデータの表示と編集作業を実際のデータソースの種類を問わずに一括してやってくれるコントロールがあると非常に便利である。
単純な編集作業ならば、これにデータバインドしてちょこちょこっとコードを書いてやるだけで、目的の仕様を達成できるのだから、ありがたくて涙が出てくる。
.NETにはデータベースにアクセスする技術が何種類かあって、どれを選ぶかによってDataGridViewとの連携のし易さもかなり変わってくる。
その辺の違いを調べる為に以下の仕様をいくつかのデータベースアクセス技術で実装して、その違いを比べてみた。
- 表示するレコードは複数のテーブルをJOINしている(JOINで対応できない場合はビューを使う)。
- 属性を変更できる。
- レコードを削除できる。
DataSet
まずはDataSetから(コードを書くスタイル)
MainForm.vb
Imports System.Text Imports System.Data.SqlClient Public Class MainForm Private Const CONNECTION_STRING = "Data Source=localhost\SQLEXPRESS;Initial Catalog=Sample;Persist Security Info=True;User ID=sa;Password=*******" Protected Overrides Sub OnLoad(ByVal e As System.EventArgs) Dim dataSet = New DataSet() Using sqlCon = New SqlConnection() With {.ConnectionString = CONNECTION_STRING} sqlCon.Open() Dim sql = New StringBuilder() sql.AppendLine("SELECT t1.Id,t1.Name,t2.Name AS Organization") sql.AppendLine("FROM Person AS t1") sql.AppendLine("INNER JOIN Organization AS t2") sql.AppendLine(" ON t1.Organization = t2.Id") Using sqlDa = New SqlDataAdapter(sql.ToString(), sqlCon) sqlDa.Fill(dataSet) End Using End Using DataGridView1.DataSource = dataSet.Tables(0) MyBase.OnLoad(e) End Sub Protected Overrides Sub OnFormClosed(ByVal e As System.Windows.Forms.FormClosedEventArgs) Dim dataTable = DirectCast(DataGridView1.DataSource, DataTable).GetChanges() ' 変更無し If dataTable Is Nothing Then Return Using sqlCon = New SqlConnection() With {.ConnectionString = CONNECTION_STRING} sqlCon.Open() Using sqlDa = New SqlDataAdapter() sqlDa.UpdateCommand = New SqlCommand("UPDATE Person SET Name = @Name WHERE Id = @Id", sqlCon) sqlDa.UpdateCommand.Parameters.Add("@Name", SqlDbType.VarChar, 50, "Name") sqlDa.UpdateCommand.Parameters.Add("@Id", SqlDbType.BigInt, 8, "Id") sqlDa.DeleteCommand = New SqlCommand("DELETE Person WHERE Id = @Id", sqlCon) sqlDa.DeleteCommand.Parameters.Add( _ New SqlParameter("@Id", SqlDbType.BigInt, 8, "Id") With {.SourceVersion = DataRowVersion.Original} _ ) sqlDa.Update(dataTable) End Using End Using MyBase.OnFormClosed(e) End Sub End Class
SqlDataAdapterを使い、最低限のコードで済ませているつもり。検索するレコードはJOINするので、SqlCommandBuilderによるUpdate文などの自動生成には対応していない。
なのでUpdateCommandとDeleteCommandは独自に設定している。
思っていたよりもコードがすっきりしていて、わかり易い。
型付けDataSet
次、型付けDataSet。こちらはデザイナによるコード生成で全てまかなえるので、コード無し。
LINQ to SQL
DLINQ
まずはLINQデザイナでテーブルをぽとぺたする。
MainForm.vb
Imports System.Data.SqlClient Public Class MainForm Private _db As SampleDatabaseDataContext = New SampleDatabaseDataContext() Protected Overrides Sub OnLoad(ByVal e As System.EventArgs) Dim dataSource = From p In _db.PersonView BindingSource1.DataSource = dataSource MyBase.OnLoad(e) End Sub Protected Overrides Sub OnFormClosed(ByVal e As FormClosedEventArgs) Dim changeSet = _db.GetChangeSet() Dim deleteSet = changeSet.Deletes.Select(Function(o As PersonView) o.Id) Dim updateSet = changeSet.Updates.Select( _ Function(o As PersonView) New With {.Id = o.Id, .Name = o.Name} _ ) For Each id In deleteSet _db.ExecuteCommand("DELETE Person WHERE Id = {0}", id) Next For Each o In updateSet _db.ExecuteCommand("UPDATE Person SET Name = {0} WHERE Id = {1}", o.Name, o.Id) Next MyBase.OnFormClosed(e) End Sub End Class
更新するところがLINQでなくなっているのが痛いけど、仕方がない。*1
以外とすっきりしている。しかし、DataSetに比べ若干敷居が高いかもしれない。
ORM(iBATIS.NET)
iBATIS.NETを使った場合
まずはPersonクラスを定義する。
Person.vb
Public Class Person Private _id As Long Public Property Id() As Long Get Return _id End Get Set(ByVal value As Long) _id = value End Set End Property Private _name As String Public Property Name() As String Get Return _name End Get Set(ByVal value As String) _name = value End Set End Property Private _organization As String Public Property Organization() As String Get Return _organization End Get Set(ByVal value As String) _organization = value End Set End Property End Class
マッピングファイル
Person.xml
<?xml version="1.0" encoding="utf-8" ?> <sqlMap namespace="OrmSample" xmlns="http://ibatis.apache.org/mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <resultMaps> <resultMap id="Person" class="OrmSample.Person, OrmSample"> <result column="Id" property="Id" /> <result column="Name" property="Name" /> <result column="Organization" property="Organization" /> </resultMap> </resultMaps> <statements> <select id="selectPerson" resultMap="Person"> SELECT t1.Id, t1.Name, t2.Name AS Organization FROM Person AS t1 INNER JOIN Organization AS t2 ON t1.Organization = t2.Id </select> </statements> </sqlMap>
画面側
MainForm.vb
Imports System.ComponentModel Imports IBatisNet.DataMapper Imports IBatisNet.DataMapper.Configuration Public Class MainForm Private _sqlMapper As ISqlMapper Protected Overrides Sub OnLoad(ByVal e As System.EventArgs) Dim sqlMapBuilder = New DomSqlMapBuilder() _sqlMapper = sqlMapBuilder.Configure() BindingSource1.DataSource = _sqlMapper.QueryForList(Of Person)("selectPerson", Nothing) MyBase.OnLoad(e) End Sub Protected Overrides Sub OnFormClosed(ByVal e As FormClosedEventArgs) ' 面倒くさい MyBase.OnFormClosed(e) End Sub End Class
どのオブジェクトが変更されたかを調べるのが面倒くさかったので更新系は実装していない。だいぶ面倒くさいのは確か。
結果をまとめると、
技術 | 参照系 | 更新系 | 総合点 (5点満点) |
雑感 |
---|---|---|---|---|
DataSet | ○ | ○ | 4 | 動的なSQLには不向き |
型付けDataSet | ○ | ○ | 4 | 動的なSQLには不向き。 デザイナを使用するため、画面のコードに直接SQLを書き込む必要がある。 |
LINQ to SQL | ○ | △ | 3.5 | イカしてる。 |
ORM | ○ | △ | 2.5 | とにかく面倒くさい。 使うならば変更を検知するなんらかの仕組みが必要 |
参照系ならばどの技術を使っても大差は無い(動的な条件に強いか弱いかぐらい)。
逆に更新系では、変更を検知する仕組みが初めから組み込まれているDataSetやDLINQはかなり強いので、ORMなんかのPONOをエンティティとして使うフレームワークはどうしても弱くなってしまう。
まぁ、DataGridView使って楽したかったらDataSetを使えって事ですね。結論でました。