SQL Server DB の 2 つのテーブルから、INNER JOIN 句を用いてデータを抽出して GridView に表示し、それを編集・更新 (UPDATE) する Web アプリを考えます。
テーブルが 1 つなら、コードは一行も書かずに SqlDataSource を利用してウィザードベースでアプリを作成できますが、2 つのテーブルを一括更新する場合はそう簡単にはいきません。
それでも、できるだけ自力でコードを書かないで、ObjectDataSource と型付 DataSet + TableAdapter をウィザードベースで作って実現できないか考えて見ました。
ベースは MSDN ライブラリのチュートリアル「Walkthrough: Performing Bulk Updates to Rows Bound to a GridView Web Server Control 」で、そのテーブルを以下の 2 つのテーブルに変更することにします。(チュートリアルには日本語版もあったのですが、リンク切れになってしまいました)
CREATE TABLE [dbo].[table-1](
[no] int NOT NULL,
[name] nvarchar(50) NOT NULL,
CONSTRAINT [PK_table-1] PRIMARY KEY CLUSTERED ([no] ASC)
)
CREATE TABLE [dbo].[table-2](
[no] int NOT NULL,
[tel] nvarchar(50) NOT NULL,
[ban] nvarchar(50) NOT NULL,
CONSTRAINT [PK_table-2] PRIMARY KEY CLUSTERED ([no] ASC)
)
まず、Visual Studio のウィザードで、型付 DataSet + TableAdapter(xsd ファイル)をつくります。以下のように、クエリビルダを使えば SELECT クエリは簡単に作成でき、それを基にした型付 DataSet も自動生成されます。
ただし、ここまでで自動生成された TableAdapter のコードには、更新に必要なメソッドは含まれていません。この先、以下のような方法で、2 つのテーブルを同時に UPDATE するコードを実装します。できるだけ自力でコードを書かないというのが条件です。
xsd ファイルを開いて、その TableAdapter にツールボックスから Query をドラッグ&ドロップ。クエリビルダで tablle-1 を UPDATE するクエリを作り、適当なメソッド名(例: UpdateQuery1)をつけて保存します。
同様に、table-2 を UPDATE するクエリを作り、適当なメソッド名(例:UpdateQuery2)をつけて保存します。結果は、以下のようになるはずです。
App_Code フォルダにクラスファイルを追加。UpdateQuery1 と UpdateQuery2 を使って2つのテーブルを UPDATE するメソッドを TableAdapter の partial class として実装します。ここは自力でコードを書く必要があります。以下のようになります。
using System;
using System.Data;
using System.Collections.Generic;
using System.Web;
using System.Transactions;
using System.ComponentModel;
namespace TwoTableDataSetTableAdapters
{
public partial class DataTable1TableAdapter
{
[DataObjectMethod(DataObjectMethodType.Update)]
public int UpdateTwoTables(int original_no, string name, string tel)
{
int returnValue;
using (TransactionScope scope =
new TransactionScope(TransactionScopeOption.RequiresNew))
{
returnValue = this.UpdateQuery1(name, original_no);
returnValue += this.UpdateQuery2(tel, original_no);
scope.Complete();
}
return returnValue;
}
}
}
ObjectDataSource の「データソースの構成」で UPDATE メソッドに上記の partial class に作ったメソッドを選択します。
GridView を上記の ObjectDataSource に接続し、参考にした MSDN ライブラリのチュートリアルに従って、Template を編集し、コードを実装します。以下のようになります。
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
// http://msdn.microsoft.com/ja-jp/library/aa992036(VS.80).aspx を参照
private bool tableCopied = false;
private DataTable originalDataTable;
// 最初の行のバインディング中に、元のデータベース値のコピーが
// DataTable オブジェクトに格納され、さらにこのオブジェクトが
// ViewState に格納されます。
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
if (!tableCopied)
{
originalDataTable =
((DataRowView)e.Row.DataItem).Row.Table.Copy();
ViewState["originalValuesDataTable"] = originalDataTable;
tableCopied = true;
}
}
}
protected void UpdateButton_Click(object sender, EventArgs e)
{
originalDataTable = (DataTable)ViewState["originalValuesDataTable"];
// GridView コントロールの行を反復処理し、各行に対してカスタムの
// IsRowModified 関数を呼び出します。
foreach (GridViewRow r in GridView1.Rows)
{
if (IsRowModified(r))
{
GridView1.UpdateRow(r.RowIndex, false);
}
}
// Rebind the Grid to repopulate the original values table.
tableCopied = false;
GridView1.DataBind();
}
// このプロシージャは、編集可能な各 TextBox コントロールの値と、
// キャッシュされた DataTable オブジェクトに格納された値の文字列
// 比較を実行します。行が変更されている場合は true を返します。
protected bool IsRowModified(GridViewRow r)
{
int currentNo = Convert.ToInt32(GridView1.DataKeys[r.RowIndex].Value);
string currentName = ((TextBox)r.FindControl("nameTextBox")).Text;
string currentTel = ((TextBox)r.FindControl("telTextBox")).Text;
// フィルタ基準と一致するすべての DataRow オブジェクトを主
// キーの順に(主キーがない場合は追加された順に) 配列として
// 取得します。
DataRow row =
originalDataTable.Select(String.Format("no = {0}", currentNo))[0];
if (!currentName.Equals(row["name"].ToString()))
{
return true;
}
if (!currentTel.Equals(row["tel"].ToString()))
{
return true;
}
return false;
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>無題のページ</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:ObjectDataSource ID="ObjectDataSource1"
runat="server"
OldValuesParameterFormatString="original_{0}"
SelectMethod="GetData"
TypeName="TwoTableDataSetTableAdapters.DataTable1TableAdapter"
UpdateMethod="UpdateTwoTables">
<UpdateParameters>
<asp:Parameter Name="original_no" Type="Int32" />
<asp:Parameter Name="name" Type="String" />
<asp:Parameter Name="tel" Type="String" />
</UpdateParameters>
</asp:ObjectDataSource>
<asp:GridView ID="GridView1"
runat="server"
AutoGenerateColumns="False"
DataKeyNames="no"
DataSourceID="ObjectDataSource1"
onrowdatabound="GridView1_RowDataBound">
<Columns>
<asp:BoundField
DataField="no"
HeaderText="no"
ReadOnly="True"
SortExpression="no" />
<asp:TemplateField
HeaderText="name"
SortExpression="name">
<ItemTemplate>
<asp:TextBox ID="nameTextBox"
runat="server"
Text='<%# Bind("name") %>'>
</asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField
HeaderText="tel"
SortExpression="tel">
<ItemTemplate>
<asp:TextBox ID="telTextBox"
runat="server"
Text='<%# Bind("tel") %>'>
</asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
<asp:Button ID="UpdateButton"
runat="server"
onclick="UpdateButton_Click"
Text="Update" />
</div>
</form>
</body>
</html>
上記のコードおよび TableAdapter の partial class は自力でコードを書く必要がありますが、その他はすべて
ウィザードベースでコードを書かずに実装できるはずです。
実行結果は、以下の画像にようになります。