WebSurfer's Home

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

TableAdapter と Transaction

by WebSurfer 2011年12月18日 20:33

Visual Studio を利用してウィザードベースで自動生成する型付 DataSet + TableAdapter を使ってデータベースを更新する際、手動トランザクション処理するにはどうしたらいいでしょうか?(MS-DTC を使う自動トランザクションではなくて)

Visual Studio 2008 から TableAdapter に Transaction プロパティが追加されたので、それと、それ以前からある Connection プロパティを使えば、Visual Studio 2005 以前と比較して手動トランザクションが容易に設定できます。

でも、Visual Studio 2008 以降を使っているなら、そもそも手動トランザクションをかけるコードを自力で書く必要はなくて、自動生成される TableAdapterManager クラスの UpdateAll メソッドを使用すればいいです。

実は知らなかったのですが、TableAdapterManager.UpdateAll メソッドのコードをよく見てみると、そのメソッドの中でトランザクションがかかるようになってました。

もともと、TableAdapterManagerは 階層更新 を実現する ため(即ち、複数のテーブルを、データベース内の制約によって定義される一貫性規則を守って関連レコードの挿入、更新、および削除を制御するため)のものということらしいですが、単一テーブルの場合もトランザクションは有効に働きます。

という訳で、Visual Studio 2008 以降なら TableAdapterManager の UpdateAll メソッドを使うのが正解だと思います。

ちなみに、Visual Studio 2005 以前の場合は Transaction プロパティは自動生成されたコードには定義されません。TableAdapter は partial クラスとして定義されているので、自動生成されたファイルとは別に、Visual Studio 2008 以降で生成されるコードを参考に、自力で Transaction プロパティを定義して使うのがいいと思います。SqlClient の場合は以下のような感じです。

private System.Data.SqlClient.SqlTransaction _transaction;

internal System.Data.SqlClient.SqlTransaction Transaction {
  get {
    return this._transaction;
  }
  set {
    this._transaction = value;
    for (int i = 0; i < this.CommandCollection.Length; i++) {
      this.CommandCollection[i].Transaction = this._transaction;
    }
    if (((this.Adapter != null) 
        && (this.Adapter.DeleteCommand != null))) {
      this.Adapter.DeleteCommand.Transaction = this._transaction;
    }
    if (((this.Adapter != null) 
        && (this.Adapter.InsertCommand != null))) {
      this.Adapter.InsertCommand.Transaction = this._transaction;
    }
    if (((this.Adapter != null) 
        && (this.Adapter.UpdateCommand != null))) {
      this.Adapter.UpdateCommand.Transaction = this._transaction;
    }
  }
}

どうも、Visual Studio 2008 から TableAdapter に Transaction プロパティが追加された理由は、TableAdapterManager.UpdateAll メソッドで手動トランザクションをかけるためのようです。

Tags: ,

ADO.NET

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

xsd ファイルの場所

by WebSurfer 2010年9月6日 13:30

ASP.NET ベースの Web アプリを作成する際、xsd ファイル(型付 DataSet + TableAtadter の定義ファイル)はどこに置いたらいいかという話です。

xsd ファイルの場所

そんなことは知っていると言われそうですね。でも、App_Code フォルダの中に置くとダメなケースがあるということはご存知でしょうか?

(1) Web サイトプロジェクト

App_Code フォルダの中以外に選択肢はありません。上の画像の左側がそれです。

これは型付 DataSet + TableAtadter に限った話ではなく、ユーザー定義のクラスやメソッドをアプリケーションのどこからでも参照できるようにするには、App_Code フォルダに置くしか方法はありません。

(2) Web アプリケーションプロジェクト

少なくとも、VB を開発言語に使っている場合は、App_Code フォルダに置くのは NG です。

App_Code の中に xsd ファイルを作ると、自動生成された Xxx.Designer.vb のメソッド名が衝突を起こし、以下のようなエラーが出ます。

"コンパイル エラー メッセージ: BC30269: 'Public Sub New()' には同じ署名で複数の定義が存在します。"

ただし、上記は VB の時だけで、C# の場合は App_Code の下に xsd ファイルを作っても問題なしでした。

自動生成された Xxx.Designer.vb, Xxx.Designer.cs のコードを比べてみると、以下の違いがありました。

  1. C# の場合は全体が名前空間(アプリケーション名.App_Code)で囲まれているが、VB の場合はそれがない。
  2. C# には VB で名前の衝突を起こした New() というメソッドがない。

アプリケーションルート直下に xsd ファイルを作れば問題なしでした。見た限り自動生成されたコードはどちらも同じなんですが。

何故ルート直下に xsd ファイルを作ると OK で、App_Code フォルダ下では NG なのか理由は調べ切れていませんが、Web アプリケーションプロジェクトでそういう制約があるようです。

というわけで、Web アプリケーションプロジェクトでは、xsd ファイルはアプリケーションルート直下に置くのがよさそうです。上の画像の右側がそれです。

------------- 2011/12/3 追記 -------------

Web アプリケーションプロジェクトで、App_Code にクラスファイルを置くと、Visual Studio と ASP.NET による二重コンパイルの問題があるそうです。なので、特にその必要がない限り、Web アプリケーションプロジェクトで App_Code フォルダを設けるのは避けた方がよさそうです。詳しくは以下のページを参照してください。

App_Code folder doesn't work with Web Application Projects (WAPs)

「特にその必要がない限り」と書きましたが、ASP.NET MVC3 の Razor で @helper メソッドを切り出して利用する場合は、現在のところ App_Code フォルダを使用せざるを得ないようです。詳しくは以下のページを参照してください。

ASP.NET MVC 3 and the @helper syntax within Razor

Tags: , ,

ASP.NET

About this blog

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

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar