WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

GridView に複数のテーブルを表示

by WebSurfer 2010年9月10日 13:10

あるテーブルを表示している GridView に 1 列追加し、そこに別テーブルからその行に関連する複数のレコード/フィールドを取得して表示するという話です。

結果は以下のような画面になります。MaterialDetails が追加した列です。

実行結果の画面

以下に具体的な例を説明します。

データベースで、「製品」は Products テーブルにて、「原料」は Materials テーブルにて管理されているとします。「製品」に使用されている「原料」の情報は Products や Materials テーブルには埋め込まず、関連を Relations という別テーブルで管理するとします。以下のような感じです。

製品テーブル (Products)
ProductID     int
ProductName   nvarchar(50)
UnitPrice     money

関連テーブル (Relations)
ProductID     int
MaterialID    int

原料テーブル (Materials)
MaterialID    int
MaterialName  nvarchar(50)
SupplierID    int

Products テーブルをベースに作った「製品」一覧の GridView に 1 列追加して、その列に、「製品」に使われている「原料」を表示します。

Materials テーブルから、関連するレコードの MaterialID, MaterialName, SupplierID をRelations テーブルを基に抽出し、追加した列に表示します。

製品 X には原料 a, b が、製品 Y には原料 a, e, f, h が使われているというように、追加した列に表示する Materials テーブルのレコード数は各行で一定ではありません。それに対応するための工夫が必要です。

具体的な手順は、以下のようになります。「原料」で表示するのが 1 フィールドだけでよいということであれば、もう少し簡単にできますが、基本的には以下の手順と同様です。

(1) Products の 型付 DataSet + TableAdapter

ソリューションにデータセット(xsd ファイル)を追加します。それにツールボックスから TableAdapter をドラッグ&ドロップします。

自動的に「TableAdapter 構成ウィザード」が起動しますので、そのウィザードを利用して Products テーブル用の型付 DataSet + TableAdapter を作成します。

基になる SELECT クエリは下記のようにします。

SELECT ProductID, ProductName, UnitPrice
FROM   Products

ウィザードが完了したら xsd ファイルを保存してください。その後、デザイナ画面のテーブル部分を右クリックして、[追加]→[列]で MaterialDetails 列を追加します。型はデフォルトで String になるはずです。

(2) Materials の型付 DataSet + TableAdapter

上記と同じ xsd ファイルの画面にツールボックスからもう一つ TableAdapter をドラッグ&ドロップし、クエリビルダで Materials テーブルと Relations テーブルの二つを追加します。

基になる SELECT クエリは下記のようにし、ウィザードを完了させます。

SELECT m.MaterialID, m.MaterialName, m.SupplierID
FROM   Materials AS m
       INNER JOIN Relations AS r
       ON m.MaterialID = r.MaterialID
WHERE  (r.ProductID = @ProductID)

上記 (1), (2) の結果は、以下の画面のようになります。

型付 DataSet + TableAdapter のデザイン画面

(3) GetProductsWithMaterials メソッド

ソリューションにクラスファイルを追加します。Products, Materials テーブルから必要なデータを抽出し、上記 (1) で作った型付 DataTable を初期化して返すメソッドを作成し、クラスファイルに実装します。

Products テーブルの TableAdapter を拡張する形で partial class にしてください。以下のような感じです。

using System;
using System.IO;
using System.ComponentModel;

namespace ProductsDataSetTableAdapters
{
  public partial class ProductsTableAdapter
  {
    [DataObjectMethod(DataObjectMethodType.Select, true)]
    public ProductsDataSet.ProductsDataTable GetProductsWithMaterials()
    {
      ProductsDataSet.ProductsDataTable prodTable = this.GetData();
      MaterialsTableAdapter adapter = new MaterialsTableAdapter();
      foreach (ProductsDataSet.ProductsRow row in prodTable.Rows)
      {
        ProductsDataSet.MaterialsDataTable mtrlTable = 
          adapter.GetData(row.ProductID);
        StringWriter writer = new StringWriter();
        mtrlTable.WriteXml(writer);
        row.MaterialDetails = writer.ToString();
      }
      return prodTable;
    }
  }
}

(4) aspx ファイル

データを表示する Web ページ(aspx ファイル)を作成します。ObjectDataSource と GridView を利用した例は下記の通りです。MaterialDetails 列の xml データは GridView の RowDataBound イベントで適宜書き換えます。

この例では、xml データを DataTable に戻して、GridView の中にもう一つ GridView を作って表示しています。GridView を使わず、文字列で表示するなど、別の方法も検討してみてください。

<%@ Page Language="C#" %>
<%@ Import Namespace="System.IO" %>
<%@ 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 GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
  {
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
      string xmlMaterialDetails = 
        (string)((DataRowView)e.Row.DataItem)["MaterialDetails"];
      StringReader reader = new StringReader(xmlMaterialDetails);
      ProductsDataSet.MaterialsDataTable table = 
        new ProductsDataSet.MaterialsDataTable();
      table.ReadXml(reader);
      GridView gv = new GridView();
      gv.DataSource = table;
      e.Row.Cells[3].Controls.Clear();
      e.Row.Cells[3].Controls.Add(gv);
      gv.DataBind();
    }
  }
</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="GetProductsWithMaterials" 
      TypeName="ProductsDataSetTableAdapters.ProductsTableAdapter">
    </asp:ObjectDataSource>
    <asp:GridView ID="GridView1" 
      runat="server" 
      AutoGenerateColumns="False" 
      DataKeyNames="ProductID" 
      DataSourceID="ObjectDataSource1" 
      OnRowDataBound="GridView1_RowDataBound">
      <Columns>
        <asp:BoundField DataField="ProductID" 
          HeaderText="ProductID" 
          InsertVisible="False" 
          ReadOnly="True" 
          SortExpression="ProductID" />
        <asp:BoundField DataField="ProductName" 
          HeaderText="ProductName" 
          SortExpression="ProductName" />
        <asp:BoundField DataField="UnitPrice" 
          HeaderText="UnitPrice" 
          SortExpression="UnitPrice" />
        <asp:BoundField DataField="MaterialDetails" 
          HeaderText="MaterialDetails" 
          SortExpression="MaterialDetails" />
      </Columns>
    </asp:GridView>
  </div>
  </form>
</body>
</html>

以上、ちょっと凝ったことをしましたが、「製品」:「原料」の関係が 1:1 もしくは 1:n (n=一定) の関係なら、3 つのテーブルを INNER JOIN した SELECT クエリをベースに、SqlDataSource と ListView で表示した方が簡単だと思います。

Tags: , ,

ASP.NET

Table の行のコピー作成

by WebSurfer 2010年9月9日 13:12

System.Web.UI.WebControls.Table の話です。

Table オブジェクトの、ある行のコピーを作って、その Table に追加するにはどうしたらいいでしょうか? 以下のような感じです。

オリジナルのテーブル 一行コピーして追加後のテーブル
オリジナル 一行コピー後

上のオリジナルの画像を作ったコードは以下の通りです。

<asp:Table id="Table1" 
    runat="server"
    CellPadding="10" 
    GridLines="Both"
    HorizontalAlign="Center">
    <asp:TableRow>
        <asp:TableCell>
            Item
        </asp:TableCell>
        <asp:TableCell>
            Description
        </asp:TableCell>
    </asp:TableRow>
    <asp:TableRow>
        <asp:TableCell>
            Row 0, Col 0
        </asp:TableCell>
        <asp:TableCell>
            Row 0, Col 1
        </asp:TableCell>
    </asp:TableRow>
    <asp:TableRow>
        <asp:TableCell>
            Row 1, Col 0
        </asp:TableCell>
        <asp:TableCell>
            Row 1, Col 1
        </asp:TableCell>
    </asp:TableRow>
</asp:Table>

この Table1 の一行目のコピーをプログラムで動的に作って、最終行の下に追加することを考えてみます。

以下のようにするとどうなるでしょう?

TableRow newRow = new TableRow();
newRow = Table1.Rows[0];
Table1.Rows.Add(newRow);

うまくいきそうに見えますが、これだと一行目が最下行の下に移動するだけです。

以下のように、TableRow だけでなく、その中身の TableCell のコピーも作って TableRow に設定し、それを Table に追加してやる必要があります。

TableRow newRow = new TableRow();
foreach (TableCell cell in Table1.Rows[0].Cells)
{
    TableCell newCell = new TableCell();
    newCell.Text = cell.Text;
    newRow.Cells.Add(newCell);
}
Table1.Rows.Add(newRow);

Table コンロトールはほとんど使わないし、実際にこのようなことをすることはなさそうなので、役に立たない話かもしれません。(汗) でも、昔、せっかく考えたことなので、忘れないように書いておきます。

Tags:

ASP.NET | ASP.NET

ValidationSummary の表示

by WebSurfer 2010年9月8日 12:27

TextBox への入力を RequiredFieldValidator, RegularExpressionValidator でチェックし、エラーメッセージを ValidationSummary で表示するようにしたとします。以下のような感じです。

SummartValidator の表示

上の画面を表示したコードは以下のようになります。

<asp:TextBox ID="TextBox1" runat="server" />  
<asp:RequiredFieldValidator 
  ID="RequiredFieldValidator1"    
  runat="server" 
  ErrorMessage="入力必須" 
  ControlToValidate="TextBox1" 
  Text="*" 
  ForeColor="Red" />
<asp:RegularExpressionValidator 
  ID="RegularExpressionValidator1" 
  runat="server" 
  ErrorMessage="正しい郵便番号が必要" 
  ControlToValidate="TextBox1" 
  ValidationExpression="\d{3}(-(\d{4}|\d{2}))?" 
  Text="*" 
  ForeColor="Red" />
<br />
<asp:Button ID="Button1" 
  runat="server" 
  Text="実行" />  
<asp:ValidationSummary ID="ValidationSummary1" 
  runat="server" 
  ShowMessageBox="True" 
  ShowSummary="False" />    

TextBox にフォーカスがあるとき Enter キーを押すと form が submit(ポストバック)されますが、この時に入力が不正だった場合、期待した動作をするでしょうか?

ちなみに、期待した動作とは以下のとおりです。

  1. ポストバックがキャンセルされる。
  2. TextBox1 の横に「*」が表示される。
  3. エラーメッセージが ValidationSummary に表示される。

上記のコードで試してみると、1, 2 は実行されますが、3 は実行されないという結果になりました。IE8, Firefox 3.6.8, Opera 10.61 で同じ結果でした。

なお、Button1 をクリック(もしくは Button1 にフォーカスを当てて Enter キーを押す)した場合は、上記 1. 2, 3 すべて実行されます。それが、上の画像です。

TextBox にフォーカス → Enter キーでは、自動生成される検証用の JavaScript の一部のコードがスキップされることが原因です。何故スキップされるのか理由は分かりませんが。

対応策としては、ハック的なのでお勧めはできませんが、以下のようにすると、TextBox にフォーカス → Enter キーでも ValidationSummary が表示されるようになります。

protected void Page_Load(object sender, EventArgs e)
{
  TextBox1.Attributes.Add("onkeydown", 
    "if (event.keyCode == 13) Page_ClientValidate();");
}

Page_ClientValidate メソッドは引数に validationGroup を取りますが、上記のように空にしておいても正しく処置してくれるようです(IsValidationGroupMatch は true 返すので)。

TextBox にフォーカス → Enter キーで ValidationSummary が表示されないようになっているのには何か理由があって、無理に上記のようなことをすると予期せぬ副作用が出るのかもしれませんので、注意してください。

---------- 2011/4/30 追記 ----------

やっぱり予期せぬ副作用がありました。(汗)

まず、検証対象の TextBox が複数ある場合、onkeydown 属性に上記のような小細工をすると ValidationSummary がダブって出てしまい、期待した動きになりません。例えば、以下のコードで、一方の TextBox の入力が正しくて、他方が NG のとき、正しい方の TextBox にフォーカスを当てて Enter キーを押すと不具合が出ます。

もうひとつの問題は、オートコンプリート機能を利用する際、ドロップダウンリストに表示される選択肢を Enter キーで確定できなくなることです。

実際に試せるように 実験室 にアプリケーションをアップしておきましたので、興味のある方は試してみてください。

<%@ Page Language="C#" %>

<!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 Page_Load(object sender, EventArgs e)
  {
    TextBox1.Attributes.Add("onkeydown", 
      "if (event.keyCode == 13) Page_ClientValidate();");
    TextBox2.Attributes.Add("onkeydown",
      "if (event.keyCode == 13) Page_ClientValidate();");
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title></title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    郵便番号:
    <asp:TextBox id="TextBox1" runat="server" />  
    <asp:RequiredFieldValidator 
      ID="RequiredFieldValidator1"    
      runat="server" 
      ErrorMessage="入力必須" 
      ControlToValidate="TextBox1" 
      Text="*" 
      ForeColor="Red" 
      Display="Dynamic" />
    <asp:RegularExpressionValidator 
      ID="RegularExpressionValidator1" 
      runat="server" 
      ErrorMessage="正しい郵便番号が必要" 
      ControlToValidate="TextBox1" 
      ValidationExpression="\d{3}(-(\d{4}|\d{2}))?" 
      Text="*" 
      ForeColor="Red" 
      Display="Dynamic" />
    <br />
    メールアドレス:
    <asp:TextBox id="TextBox2" runat="server" />  
    <asp:RequiredFieldValidator 
      ID="RequiredFieldValidator2"    
      runat="server" 
      ErrorMessage="入力必須" 
      ControlToValidate="TextBox2" 
      Text="*" 
      ForeColor="Red" 
      Display="Dynamic" />
    <asp:RegularExpressionValidator 
      ID="RegularExpressionValidator2" 
      runat="server" 
      ErrorMessage="正しいメールアドレスが必要" 
      ControlToValidate="TextBox2" 
      ValidationExpression="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*" 
      Text="*" 
      ForeColor="Red" 
      Display="Dynamic" />
    <br />
    <asp:Button id="Button2" 
      runat="server" 
      Text="検証" />  
    <asp:ValidationSummary id="ValidationSummary1" 
      runat="server" 
      ShowMessageBox="True" 
      ShowSummary="False" 
      HeaderText="サマリーです" />
  </div>
  </form>
</body>
</html>

Tags:

Validation

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年3月  >>
252627282912
3456789
10111213141516
17181920212223
24252627282930
31123456

View posts in large calendar