Architect's Log

I'm a Cloud Architect. I'm highly motivated to reduce toils with driving DevOps.

ObjectDataSourceにはDataTableを双方向でデータバインドできない

ObjectDataSourceにはDataTableを双方向でデータバインドできません。

どういうこと?

以下のサンプルで説明します。

Default.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication3._Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<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" 
            DataObjectTypeName="WebApplication3.HogeDataSet+hogeDataTable" 
            DeleteMethod="Update" InsertMethod="Update" SelectMethod="GetData" 
            TypeName="WebApplication3.DataAccesser" UpdateMethod="Update"></asp:ObjectDataSource>
    </div>
    <asp:DetailsView ID="DetailsView1" runat="server" AllowPaging="True" 
        AutoGenerateRows="False" DataSourceID="ObjectDataSource1" Height="50px" 
        Width="125px">
        <Fields>
            <asp:BoundField DataField="col1" HeaderText="col1" SortExpression="col1" />
            <asp:BoundField DataField="col2" HeaderText="col2" SortExpression="col2" />
            <asp:CommandField ShowInsertButton="True" />
        </Fields>
    </asp:DetailsView>
    </form>
</body>
</html>
Default.aspx.cs
using WebApplication3.HogeDataSetTableAdapters;

namespace WebApplication3 {
    public partial class _Default : System.Web.UI.Page {
    }

    public class DataAccesser {
        public HogeDataSet.hogeDataTable GetData() {
            // HogeDataSetは型指定されたDataSetです。
            HogeDataSet.hogeDataTable t = new HogeDataSet.hogeDataTable();
            using(hogeTableAdapter a = new hogeTableAdapter()) {
                a.Fill(t);
            }
            return t;
        }

        public void Update(HogeDataSet.hogeDataTable dataTable) {
            using(hogeTableAdapter a = new hogeTableAdapter()) {
                a.Update(dataTable);
            }            
        }
    }
}
データ表示


データ表示は問題なくできます。

データ登録


しかし、データ登録をすると以下の例外が発生します。
"ObjectDataSource 'ObjectDataSource1' の DataObjectTypeName プロパティによって指定された型上の 'col1' というプロパティが見つかりませんでした。"

どうして?

DataTableに 'col1' というプロパティがないからです。

どうすれば?

Update、Delete、Insertを実装する場合は、面倒でもデータクラスを作成します。

Default.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication3._Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<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" 
            DataObjectTypeName="WebApplication3.Hoge" DeleteMethod="UpdateList" 
            InsertMethod="UpdateList" SelectMethod="GetList" 
            TypeName="WebApplication3.DataAccesser" UpdateMethod="UpdateList"></asp:ObjectDataSource>
    </div>
    <asp:DetailsView ID="DetailsView1" runat="server" AllowPaging="True" 
        AutoGenerateRows="False" DataSourceID="ObjectDataSource1" Height="50px" 
        Width="125px">
        <Fields>
            <asp:BoundField DataField="col1" HeaderText="col1" SortExpression="col1" />
            <asp:BoundField DataField="col2" HeaderText="col2" SortExpression="col2" />
            <asp:CommandField ShowDeleteButton="True" ShowEditButton="True" 
                ShowInsertButton="True" />
        </Fields>
    </asp:DetailsView>
    </form>
</body>
</html>
Default.aspx.cs
using System.Collections.Generic;
using System.Web.UI.MobileControls;

namespace WebApplication3 {
    public partial class _Default : System.Web.UI.Page {
    }

    public class DataAccesser {
        public List<Hoge> GetList() {
            List<Hoge> hogeList = new List<Hoge>() {
                new Hoge() { col1 = 0, col2 = 1 },
                new Hoge() { col1 = 10, col2 = 11 },
            };

            return hogeList;
        }

        public void UpdateList(Hoge hoge) {
            // 更新処理を実装する
        }
    }

    /// <summary>
    /// 必要なプロパティを実装したデータクラス。
    /// </summary>
    public class Hoge {
        public int col1 { get; set; }
        public int col2 { get; set; }
    }
}

これで更新できるようになります。

ちなみに

スコット・ガスリー氏のブログでは以下のように述べられています。
Webフォームモデルバインディング パート1:データの選択 (ASP.NET vNextシリーズ) (1/2):CodeZine
本稿は、Scott Guthrie氏のブログを、氏の許可を得て、翻訳、転載したものです。米Microsoft社の副社長で、ASP.NETやSilverlightの開発チームを統率する氏のブログでは、次期製品を含む最新の技術をいち早く紹介しています。 ...

現在利用できる別のオプションは、ObjectDataSourceコントロールの使用です。このコントロールは、データアクセス層から、UIコードをよりクリーンに分別し、データコントロールがページングやソートなどの自動機能を提供できるようにします。しかし、データの選択では上手くいっても、双方向のデータバインディングを実施した時はまだ面倒で、簡単な(複雑な型の”深い”バインディングのない)プロパティだけしかサポートしなかったり、(エラー検証なども含む)多くのシナリオを処理するために、多くの複雑なコードを書かなければいけなかったりします。

以降2011/10/05追記

データクラスの代わりに型指定したDataRowが使えるのでは?

DataRowは引数のないPublicなコンストラクタがないので駄目です。

ObjectDataSource クラス (System.Web.UI.WebControls)
多階層 Web アプリケーション アーキテクチャで、データ バインド コントロールにデータを提供するビジネス オブジェクトを表します。 ...

ObjectDataSource コントロールは、リフレクションを使用してビジネス オブジェクトのインスタンスを作成し、作成したインスタンスに対してメソッドを呼び出してデータを更新、挿入、および削除します。