GridView や DetailsView などのデータバインドコントロールを使ってレコードの更新や挿入を行う場合、データを DropDownList の一覧から選択してもらうことがあります。
このとき、DropDownList の SelectedValue プロパティに '<%# Bind("xxx") %>' のようにデータバインドメソッドを設定すると、最初に更新画面を表示するときはオリジナルのデータで DropDownList のアイテムが選択されて表示され、異なるアイテムを選択して更新をかけるとそのアイテムの Value でデータベースを更新できます。
ただし、2 つの DropDownList を配置して、それを連動させる場合は以下のような問題があり、注意が必要です。
例えば、上の画像に示すように、DetailsView に 2 つの DropDownList を配置し、1 つめの DropDownList で飲み物というカテゴリーを選択すると、2 つめの DropDownList には飲み物に分類される商品が絞り込まれて表示されるケースを考えます。
2 つの DropDownList の SelectedValue プロパティに、それぞれ '<%# Bind("CategoryID") %>' および '<%# Bind("ProductID") %>' というようなデータバインドメソッドが設定してあるとします。
ReadOnly モードで表示された状態から[編集]ボタンをクリックして Edit モードに移った直後は、DropDownList に正しくアイテムが選択されて表示されますが、1 つめの DropDownList のアイテムの選択を変更すると、"Eval()、XPath()、および Bind() のようなデータバインド メソッドは、データバインドされたコントロールのコンテキストでのみ使用することができます。" というエラーになります。
(ArgumentOutOfRangeException で "項目一覧に存在しないため、'DropDownList2' に SelectedValue を指定することは無効です。" になるのかと思いましたがそうではなかったです。詳細は不明ですが、Page.GetDataItem メソッドでデータ バインディングコンテキストを取得できないのが原因らしいです。)
エラーを回避するには、2 つめの DropDownList の SelectedValue='<%# Bind("ProductID") %>' を削除すればよいです。
ただし、削除すると、オリジナルのデータが DropDownList で選択・表示されず、かつ、データベースの更新ができなくなります。
その問題の解決策は、DetailsView の DataBound イベントハンドラで DropDownList の SelectedValue を設定すること、および、SqlDataSource の Updating イベントハンドラで SqlParameter に直接 SelectedValue を代入することです。
以下に、DetailsView に 2 つの DropDownList を配置し、それらを連動させた場合のサンプルコードをアップしておきます。Products と Categories テーブルは Northwind サンプルデータベースのものです。Sales テーブルは自作でスキーマは以下の通りです。
CREATE TABLE [dbo].[Sales](
[ID] [int] IDENTITY(1,1) NOT NULL,
[CategoryID] [int] NOT NULL,
[ProductID] [int] NOT NULL,
CONSTRAINT [PK_Sales] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF ・・・中略・・・)
)
aspx ページのコードは以下の通りです。
<%@ 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">
protected void DetailsView1_DataBound(object sender, EventArgs e)
{
// DropDownList2 の SelectedValue プロパテ
// ィに '<%# Bind("ProductID") %>' を設定
// しない代わりに以下のようにする。
if (DetailsView1.CurrentMode == DetailsViewMode.Edit)
{
DropDownList ddl =
(DropDownList)DetailsView1.FindControl("DropDownList2");
ddl.SelectedValue =
((DataRowView)DetailsView1.DataItem)["ProductID"].ToString();
}
// Insert モードでは DetailsView1.DataItem が
// null なので SelectedValue の設定はできない。
// と言うより、そもそも Insert モードでは設定
// する意味はない。
}
protected void SqlDataSource1_Updating(object sender,
SqlDataSourceCommandEventArgs e)
{
// SelectedValue='<%# Bind("ProductID") %>'
// としていないので、以下のようにパラメータ
// に直接 SelectedValue を代入する。
DropDownList ddl =
(DropDownList)DetailsView1.FindControl("DropDownList2");
e.Command.Parameters["@ProductID"].Value = ddl.SelectedValue;
}
protected void SqlDataSource1_Inserting(object sender,
SqlDataSourceCommandEventArgs e)
{
DropDownList ddl =
(DropDownList)DetailsView1.FindControl("DropDownList3");
e.Command.Parameters["@CategoryID"].Value = ddl.SelectedValue;
ddl = (DropDownList)DetailsView1.FindControl("DropDownList4");
e.Command.Parameters["@ProductID"].Value = ddl.SelectedValue;
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:SqlDataSource ID="SqlDataSource1" runat="server"
ConnectionString="<%$ ConnectionStrings:Northwind %>"
DeleteCommand="DELETE FROM [Sales] WHERE ([ID] = @ID)"
InsertCommand="INSERT INTO [Sales] ([CategoryID], [ProductID])
VALUES (@CategoryID, @ProductID)"
SelectCommand= "SELECT s.ID, s.CategoryID, s.ProductID, c.CategoryName, p.ProductName
FROM Sales AS s
INNER JOIN Categories AS c ON s.CategoryID = c.CategoryID
INNER JOIN Products AS p ON s.ProductID = p.ProductID"
UpdateCommand="UPDATE [Sales]
SET [CategoryID] = @CategoryID, [ProductID] = @ProductID
WHERE ([ID] = @ID)"
OnUpdating="SqlDataSource1_Updating"
OnInserting="SqlDataSource1_Inserting">
<DeleteParameters>
<asp:Parameter Name="ID" Type="Int32" />
</DeleteParameters>
<InsertParameters>
<asp:Parameter Name="CategoryID" Type="Int32" />
<asp:Parameter Name="ProductID" Type="Int32" />
</InsertParameters>
<UpdateParameters>
<asp:Parameter Name="CategoryID" Type="Int32" />
<asp:Parameter Name="ProductID" Type="Int32" />
<asp:Parameter Name="ID" Type="Int32" />
</UpdateParameters>
</asp:SqlDataSource>
<asp:DetailsView ID="DetailsView1"
runat="server" AllowPaging="True"
AutoGenerateRows="False"
DataKeyNames="ID"
DataSourceID="SqlDataSource1"
EnableModelValidation="True"
OnDataBound="DetailsView1_DataBound">
<Fields>
<asp:BoundField DataField="ID"
HeaderText="ID"
InsertVisible="False"
ReadOnly="True"
SortExpression="ID" />
<asp:TemplateField HeaderText="CategoryName"
SortExpression="CategoryName">
<EditItemTemplate>
<asp:SqlDataSource ID="SqlDataSource2"
runat="server"
ConnectionString="<%$ ConnectionStrings:Northwind %>"
SelectCommand="SELECT [CategoryID], [CategoryName]
FROM [Categories]">
</asp:SqlDataSource>
<asp:DropDownList ID="DropDownList1"
runat="server"
DataSourceID="SqlDataSource2"
DataTextField="CategoryName"
DataValueField="CategoryID"
AutoPostBack="True"
SelectedValue='<%# Bind("CategoryID") %>'>
</asp:DropDownList>
</EditItemTemplate>
<InsertItemTemplate>
<asp:SqlDataSource ID="SqlDataSource4"
runat="server"
ConnectionString="<%$ ConnectionStrings:Northwind %>"
SelectCommand="SELECT [CategoryID], [CategoryName]
FROM [Categories]">
</asp:SqlDataSource>
<asp:DropDownList ID="DropDownList3"
runat="server"
DataSourceID="SqlDataSource4"
DataTextField="CategoryName"
DataValueField="CategoryID"
AutoPostBack="True">
</asp:DropDownList>
</InsertItemTemplate>
<ItemTemplate>
<asp:Label ID="Label1"
runat="server"
Text='<%# Bind("CategoryName") %>'>
</asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="ProductName"
SortExpression="ProductName">
<EditItemTemplate>
<asp:SqlDataSource ID="SqlDataSource3"
runat="server"
ConnectionString="<%$ ConnectionStrings:Northwind %>"
SelectCommand="SELECT [ProductID], [ProductName]
FROM [Products]
WHERE ([CategoryID] = @CategoryID)">
<SelectParameters>
<asp:ControlParameter
ControlID="DropDownList1"
Name="CategoryID"
PropertyName="SelectedValue"
Type="Int32" />
</SelectParameters>
</asp:SqlDataSource>
<%-- SelectedValue='<%# Bind("ProductID") %>'
と設定するのは NG --%>
<asp:DropDownList ID="DropDownList2"
runat="server"
DataSourceID="SqlDataSource3"
DataTextField="ProductName"
DataValueField="ProductID">
</asp:DropDownList>
</EditItemTemplate>
<InsertItemTemplate>
<asp:SqlDataSource ID="SqlDataSource5"
runat="server"
ConnectionString="<%$ ConnectionStrings:Northwind %>"
SelectCommand="SELECT [ProductID], [ProductName]
FROM [Products]
WHERE ([CategoryID] = @CategoryID)">
<SelectParameters>
<asp:ControlParameter
ControlID="DropDownList3"
Name="CategoryID"
PropertyName="SelectedValue"
Type="Int32" />
</SelectParameters>
</asp:SqlDataSource>
<asp:DropDownList ID="DropDownList4"
runat="server"
DataSourceID="SqlDataSource5"
DataTextField="ProductName"
DataValueField="ProductID">
</asp:DropDownList>
</InsertItemTemplate>
<ItemTemplate>
<asp:Label ID="Label2"
runat="server"
Text='<%# Bind("ProductName") %>'>
</asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:CommandField
ShowDeleteButton="True"
ShowEditButton="True"
ShowInsertButton="True" />
</Fields>
</asp:DetailsView>
</div>
</form>
</body>
</html>