Share via


第 3 章 「商品カタログ ~ CatalogProducts ~」~ Commerce Server 2002 で学ぶ ASP.NET ~

NRI ラーニングネットワーク株式会社
溝端 二三雄

2003 年 2 月 3 日

目次

3-1 商品カタログの概要 3-1 商品カタログの概要
   3-1-1 商品カタログとは3-1-1 商品カタログとは
   3-1-2 商品カタログクラス3-1-2 商品カタログクラス
3-2 DataSet3-2 DataSet
   3-2-1 非接続3-2-1 非接続
   3-2-2 DataTable3-2-2 DataTable
   3-2-3 XML のサポート3-2-3 XML のサポート
3-3 DataSet の使用3-3 DataSet の使用
   3-3-1 DataSet の作成例3-3-1 DataSet の作成例
   3-3-2 DataSet の利用例3-3-2 DataSet の利用例
3-4 型指定された DataSet3-4 型指定された DataSet
   3-4-1 もう一度データセットの作成3-4-1 もう一度データセットの作成
   3-4-2 データセット派生クラス3-4-2 データセット派生クラス
   3-4-3 DataSet 派生クラスの必要性3-4-3 DataSet 派生クラスの必要性
3-5 XML ファイルからのカタログインポート3-5 XML ファイルからのカタログインポート
   3-5-1 XML Web サービス概要3-5-1 XML Web サービス概要
   3-5-2 XML Web サービスの作成3-5-2 XML Web サービスの作成
   3-5-3 XML Web サービスの利用3-5-3 XML Web サービスの利用
   3-5-4 DataSet に XML データをロード3-5-4 DataSet に XML データをロード
   3-5-5 XML データをテーブルに挿入3-5-5 XML データをテーブルに挿入
   3-5-6 動作確認3-5-6 動作確認

3-1 商品カタログの概要

この章の目的は ADO.NET の中心ともいえる DataSet の理解です。DataSet の機能をサンプルアプリケーションの商品カタログシステムを例に解説します。

3-1-1 商品カタログとは

今回のサンプルアプリケーションで使用する商品カタログは以下のような仕組みです。

図

3-1 商品カタログの構造

商品データに相当するのが Product であり、複数の Product はいずれか 1 つの Category に所属します。つまり、Category はユーザーが商品を検索しやすいようにクループ分けしています。同じ種類の Category をまとめたのが Catalog です。具体的に例を挙げて解説すると、「布皮木製チェアセット」 や 「木製チェア」 という商品は 「椅子」 というカテゴリに所属しています。「椅子」 以外にも 「机」 や 「長椅子」 というカテゴリが 「Furniture」 という家具カタログとして存在します。

このサンプルアプリケーションはできる限りシンプルなものにするために Microsoft Commerce Server2002 で提供されるような一般的なカタログの機能を実装していないものがあります。以下の機能は実装していません。

  • 複数のカタログ:クラスとしては機能を提供していますが、UI(Web フォーム)がこの機能を利用していません。家具カタログのみを表示するようになっています。

  • カタログの設定:Profile との連携の実現。例えば、特定のユーザーにしか表示されないカタログを定義したり、特定のユーザーであれば割引価格が適用された料金が表示されたりする機能。

  • 1 つの商品が複数のカテゴリに属する: 商品の特性上、カテゴリに分割できないものは複数のカテゴリに属することがよくあります。例えば、椅子と机がセットになっている商品は 「椅子」 と 「机」 の両方のカテゴリに属したほうが適切です。

  • カテゴリの階層構造:取り扱う商品が多くなるとカテゴリ内の商品も多くなり、ユーザーの検索を妨げます。この場合例えば、「椅子」 カテゴリの中にさらに 「長椅子」 「ソファー」 「ビジネス用」 などカテゴリの階層を作ってカテゴリ内の商品を減らします。

3-1-2 商品カタログクラス

商品カタログで使用しているクラスを見てみます。図 3-1 商品カタログクラス がその概要です。Profile クラスよりもさらにシンプルな構成です。

3-1-2

3-1-2 商品カタログクラス

Profile クラスよりシンプルなため柔軟性に欠ける部分はあります。ただし、DataSet の機能を理解するにはこれで十分であり、データベースのスキーマの変更への対応も、クラス設計で対応するのではなく DataSet の機能を使用した対応方法を解説します。

寄り道、わき道、まわり道 (3) xxxContext クラス

Profile システムのクラスにも ProfileContext というクラスが存在しましたが、実は今回のサンプルアプリケーションでは存在する価値がありません。ProfileContext クラスは Profile オブジェクトを作成する機能しか提供していなかったからです。 つまり Profile クラスを直接インスタンス化すればいいので、このクラスはなくてもかまいません。Microsoft Commerce Server 2002 では xxxContext という名前のクラスによって特定の機能や特定のユーザーなどに関連した機能を簡単に使用できるようになっています。例えば CatalogContext クラスによって全てのカタログの中から、「現在のユーザー」 に関連付けられたカタログだけのリストを簡単に取得することができます。今回のサンプルアプリケーションではこのような機能は省略しています。つまり CatalogContext クラスの CatalogName プロパティはシステム内に存在する全てのカタログのリストを取得します。とは言えこのプロパティは重要です。システムが提供するカタログ機能を使用するためには「利用可能なカタログ名」 が取得できないと実際のカタログデータにアクセスできないからです。しかし、実際にカタログオブジェクト (ProductCatalog) を取得する GetCatalog メソッドは管理用の別のクラス (CatalogManager など) にあるべきメソッドですが、このサンプルアプリケーションでは他の管理機能 (カタログを作成したり削除したりする機能など) を提供していないのでコンテキストクラスに含めています。

3-1-3 ProductCatalog クラス

このクラスが実際の商品カタログのクラスです。しかし、Profile クラスと大きく異なる点があります。Profile クラスにはユーザーのプロファイル情報が全て格納することができました。言い換えると、特定のユーザーの Profile オブジェクトのプロパティとしてユーザー情報を全て保持していたわけです。ProductCatalog クラスを同様に実現すると大変なことになります。商品カタログは場合によっては数百、数千もの商品データが格納されます。これらをプロパティとして保持するとかなりのメモリを消費することは間違いありません。ですので、ProductCatalog クラスはカタログの構成要素であるカテゴリや商品データにアクセスする手段を提供することを目的としたクラスです。

  • GetCategories: このカタログ内にあるカテゴリのリストを返します。

  • GetProductList: 引数として渡したカテゴリに属する商品のリストを返します。

  • GetProduct: 引数として渡した商品番号 (ProductID) に対応した商品データを返します。

3-2 DataSet

ProductCatalog が提供するメソッドの役割はメソッド名である程度想像がつくかと思いますが、問題は各メソッドの戻り値のデータ型 (クラス) です。xxxDataTable というデータ型の戻り値を返すことになっています。このデータ型は DataSet の機能の一部です。詳細は 3-3 型指定された DataSet で解説しますが、その前に DataSet の概要を解説しておきます。

DataSet はデータの入れ物です。DataReader もデータの入れ物として第2章で使用しました。ただし、DataReader はその名の通りデータを読み取ることしかできません。では DataSet はデータを書き込むことができるのでしょうか。答えは 「できます」 となります。これだけだと ADO をご存知の方は 「レコードセットと同じようなもの ?」 と考えるかもしれません。確かに ADO レコードセットと同様の機能は提供します。しかし、それだけではないのです。DataSet の大きな特徴を 3 点挙げます

3-2-1 非接続

データセットに入れられたデータはデータソースとの接続を切断します。もちろん、データセットにデータを充填する際にはデータソースに接続しますが、この作業が終わり次第その接続が切断されるのです。これによるメリットはスケーラビリティのあるアプリケーションを簡単に作成できることです。データベースへの接続はアプリケーションにとってもデータベースサーバーにとっても負荷のかかる作業です。また、データの更新を可能にする場合、データベース側でデータにロックをする必要があり、同時実効性を著しく低下させます。DataSet を使用すれば、開発者はこれらの点に関して一切考慮する必要はありません。

また、データベースにアクセスした結果を他のクラスや他のアプリケーションに渡したいことが頻繁にあります。特に Select 文を実行した結果を渡す場合、データソースと接続されている ADO レコードセットをしようすると、データが充填されたレコードセットオブジェクトをそのまま渡すことはしません。これは、渡した先の処理が終わるまでは接続を保持する必要があり、その処理が短時間で終わることがわからない場合現実的ではありません。そのため開発者は入れ物 (レコードセット) に入っているデータを別の入れ物 (配列や構造体) にコピーして使用していたはずです。非接続型の DataSet はそんな心配は不要です。例えばXML Webサービス (この章で後述) の引数や戻り値としてそのまま使用しても何の問題もありません。

3-2-2 DataTable

DataSet には複数のデータソースのテーブルを保持できます。ADO レコードセットでは1つしか保持できなかったため、あらかじめジョイン (結合) した結果を 1 つのテーブルとして保持していました。DataSet を使用するとこの必要がなくなるのでデータベースプログラミングの負荷が減ります。また、複数の作業で使用するデータを 1 つの DataSet にまとめて別のアプリケーションに渡すこともできます。さらに、XML データとリレーショナルデータベースのデータを同じ DataSet に保持することもできます。

もちろん、DataSet 内の複数のテーブル間にはリレーションの設定もできます。リレーションは同じデータソースのテーブル間はもちろんのこと、異なるデータソースのテーブル(例えば XML データと RDB のデータ)間であっても可能です。

3-2-3 XML のサポート

ADO にも XML をサポートする機能が組み込まれていましたが、DataSet はさらに拡張しています。このため、非常に簡単に XML データを扱うことができます。例えば、XML ファイルのデータを DataSet 内のテーブルとして読み込む場合、ReadXml メソッドを使用して引数に XML ファイルのパスを渡すだけで DataTable として読み込むことが可能です。DataTable として読み込まれた XML データは、XML データであることを意識する必要はありません。つまり、DOM 等のプログラミングを知らなくてもこのデータにアクセスできます。

3-3 DataSet の使用

機能が豊富になれば使用方法が複雑になることはコンピュータの世界だけでなく、一般的によくある現実です。しかし、DataSet はそうではありません。データソースと非接続形式でデータテーブルとしてデータを充填する機能は DataAdapter が全て実行してくれます。開発者はこの DataAdapter オブジェクトに対し、データソースとの接続情報 (Connection) とデータにアクセスするコマンド (Command) を設定します。ここまでは ADO であっても DataReader の使用であっても同じ設定です。DataSet 特有なのはこの後の作業です。Connection の Open メソッドや Command の実行メソッドの代わりに DataAdapter の Fill メソッドを使用します。たったこれだけで、機能強化されたデータの入れ物である DataSet が作成されます。

3-3-1 DataSet

3-3-1 DataSet の作成例

実際に単純な DataSet を作成するコードを DataReader を使用した場合と比較してみます。サンプルアプリケーションではデータベースにアクセスするコードを単独のアセンブリ (DataAccess.dll) として作成しています。これによりデータベースへの接続文字列を 1 ヶ所で定義することができ、必要であればこのアセンブリを別のアプリケーションでも使用できるようになります。

データアクセスコンポーネント用のプロジェクト (DataAccess) 内の DBAccess クラスの定義の中から、SelectCatalogName メソッドと SelectCategories メソッドの内容を確認してください。

SelectCatalogName メソッドはシステム内に存在するカタログ名のリストを取得します (今回は Furniture しか存在しません)。選択したデータは DataReader を使用して処理しています。DataReader は接続型のデータアクセスを提供するので、このメソッドを使用するアプリケーションが取得したリストを処理するのにどれだけの時間がかかるかわからないので、配列 (ArrayList) に取得したデータをコピーして戻り値として使用しています (太字部分がそのコードです)。

   Public Function SelectCatalogName() As ArrayList
        Dim arr = New ArrayList()
        Dim cn As New SqlConnection(ConnectionString)
        Dim drd As SqlDataReader
        Dim cmd As SqlCommand = New SqlCommand("SELECT CatalogName FROM CatalogProducts GROUP BY CatalogName", cn)
        cn.Open()
        drd = cmd.ExecuteReader()
        Do While drd.Read()
            arr.Add(drd.GetString(0))
        Loop
        drd.Close()
        cn.Close()
        Return arr
    End Function

同様の処理をデータセットを使用した例が SelectCategories メソッドです。このメソッドは引数として渡されたカタログ名を元に、そのカタログ内に存在するカテゴリのリストを取得します。cmdCatalogProducts という DataAdapter オブジェクトを作成する際のコンストラクタ引数として実行するコマンドと接続文字列を渡しています。これだけで DataAdapter は内部的に Connection オブジェクトと Command オブジェクトを作成します。後は Fill メソッドを実行する際、データの入れ物である DataSet オブジェクトと DataSet オブジェクト内に作成される DataTable オブジェクトの名前を引数として渡すだけです。DataTable オブジェクトの名前はデータソースのテーブル名や列名と同じにする必要はありません。データが充填されたら、DataSet オブジェクト (ds) を戻り値として返すことができます(今回は DataSet ではなく、その中の DataTable を戻り値として使用しています)。

    Public Function SelectCategories(ByVal CatalogName As String) As 
DsProducts.CategoriesDataTable
        Dim cmdCatalogProducts As SqlDataAdapter = New SqlDataAdapter("SELECT CategoryName 
FROM CatalogProducts WHERE CatalogName = '" & CatalogName & "' GROUP BY CategoryName", 
ConnectionString)
        Dim ds As New DsProducts()
        Dim dtCategories As DsProducts.CategoriesDataTable
        cmdCatalogProducts.Fill(ds, "Categories")
        dtCategories = ds.Categories
        Return dtCategories
    End Function

単に文字列型の配列を返すメソッドなので DataSet を使用しなくても良いかもしれません (理由は後述)。ただ、上記の 2 つのメソッドを比較してみても、コードが単純であり Connection の Open、Close も必要ないので開発者の負担が少なくなることがわかります。

3-3-2 DataSet の利用例

DataSet 内の DataTable は配列としての機能を提供してくれます。これにより DataTable オブジェクトはデータバインドコントロールで使用可能です。

以下のコードは ProductCatalog クラスの GetCategories メソッドの定義です。このメソッドは DataAccess コンポーネントの SelectCategories メソッドを呼び出し、その戻り値をそのまま返しているだけです。

    Public Function GetCategories() As DsProducts.CategoriesDataTable
        Dim db As New DBAccess()
        Dim myProducts As DsProducts.CategoriesDataTable
        myProducts = db.SelectCategories(myCatalogName)
        Return myProducts
    End Function 

上記メソッドは /Fabrikam/Contents/CategoryPage.aspx ページの Page_Load イベントプロシージャで使用されています。CategoryDataList はデータリストコントロールで、カテゴリ名を表形式に表示します。ProductCatalog オブジェクトの GetCategories メソッドの戻り値である DataTable オブジェクトをデータリストコントロールの DataSource プロパティにセットするだけで、配列の機能 (IEnumerable) を使用して結果を表示してくれます。

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' ページを初期化する ユーザー コードをここに挿入します。
        Dim myCatalogContxt As New CatalogContext()
        Dim catalog As ProductCatalog
        catalog = myCatalogContxt.GetCatalog("Furniture")
        CatalogIDLabel.Text = catalog.Name()
        CategoryDataList.DataSource = catalog.GetCategories()
        CategoryDataList.DataBind()
    End Sub

図

3-3 DataSet の内容を DataList コントロールで表示

3-4 型指定された DataSet

3-3-1 DataSet の作成例で確認したサンプルコード (GetCategoriesメソッド) は DataSet オブジェクト (ds) を戻り値として使用せずに DataTable(dtCategories) を使用しました。しかし、この解説は厳密には誤りです。

dtCategories 変数の定義を確認してください。DataTable 型ではなく CategoriesDataTable 型になっています。

Dim dtCategories As DsProducts.CategoriesDataTable

DataTable 型の変数は以下のように定義します。

Dim dt As DataTable

CategoriesDataTable 型とは何でしょう。 「データテーブル クラス」 と言っても間違いではありませんが、厳密には 「データテーブル 派生クラス」 と言うべきです (ただ、一般的には 「データテーブル」 と呼ばれています)。

3-4-1 もう一度データセットの作成

これまでに解説したデータセットの作成方法はコードを使用したものでした。Visual Studio .NET にはこれを GUI で作成する機能があります。以下の手順で実験できます。

  1. Fabrikam プロジェクトに新しく Web フォームを追加します

  2. サーバーエクスプローラで〔サーバー〕-〔コンピュータ名〕-〔SQL Server〕-〔コンピュータ名〕-〔Fabrikam〕-〔テーブル〕を展開します。

  3. CatalogProducts テーブルをデザイン画面にドロップします。

  4. これで、SqlDataAdapter が作成されます。

  5. デザイン画面下部に作成された SqlDataAdapter クラスのインスタンス (オブジェクト) である SqlDataAdapter1 を選択し、〔データ〕メニューから〔データセットの生成〕コマンドを実行します。

  6. DataSet1 という名前でデータセットクラス (厳密には DataSet 派生クラス) が定義され、SqlDataAdapter1 によって取得されるデータがデータテーブルとして作成される設定になっています。〔OK〕 ボタンをクリックします。

  7. デザイン画面下部に DataSet1 クラスのインスタンス (オブジェクト) である DataSet11 が作成されます。

    上記の手順で DataAdapter や DataSet が作成できます。後は適切なタイミング (Page_Load やボタンのクリックイベント) で DataAdapter の Fill メソッドを実行するようにコードを 1 行書くだけです。

3-4-2 データセット派生クラス

先ほどの DataSet をウィザードで作成する実験を実行すると、プロジェクトエクスプローラに DataSet1.xsd という名前のファイルが Visual Studio .NET によって作成されます。さらにその下に DataSet1.vb ファイルも作成されます (〔全てのファイルを表示〕を実行すると確認できます)。DataSet1.xsd を表示すると下図のように XSD で書かれたスキーマとそのデザイン画面が表示されます(〔データセット〕タブと 〔XML〕 タブで切り替え)。

図

3-4 DataSet スキーマ

この XSD(XML Schema Definition) スキーマが DataSet1 クラスの型情報 (クラス定義) の一部です。DataSet は XML との親和性を高めるためにクラスの定義を XSD ファイルとして保存しています。

また、DataSet1.vb ファイルの内容を確認してください。これがアセンブリとなるDataSet1クラスの型情報 (クラス定義) です。以下のクラス定義が確認できます。

Public Class DataSet1
    Inherits DataSet
    Private tableCatalogProducts As CatalogProductsDataTable
    ・・・以下、省略・・・ 

DataSet クラスから派生した DataSet1 クラスの中に CatalogProductsDataTable 型の変数が定義されています。CatalogProductsDataTable クラスの定義も確認できます。このクラスは DataTable から派生するように定義されており、XML データとしての機能を提供すためのメソッドや、列 (Column) や行 (Row) にアクセスするためのプロパティも定義されています。また、DataSet はデータへの変更が可能です。このために必要なプロパティ、メソッド、イベントも定義されています。

これが DataSet 派生クラスであり、そのメンバーである DataTable 派生クラスや DataRow 派生クラスの定義です。コードを 1 行ずつ追いかけなくても全体のコードの量を見れば数多くの機能が提供されることが想像できると思います。3-3-1 DataSet の作成例でカテゴリのリストを取得する際に DataSet を使用しなくても良いのではないか。と書きましたが、その理由がこの派生クラスです。読み取り専用の単なる文字列型の配列であれば、派生クラスが提供する機能をほとんど使用することが無いからです。ではこのDataSet派生クラスが提供する機能のポイントを解説します。

3-4-3 DataSet 派生クラスの必要性

Visual Studio .NET はなぜ DataSet 派生クラスを生成するのでしょう。このクラスにより以下の大変便利な機能が提供されます。

  • 安全なデータ型の提供

  • インテリセンスの提供

  • XMLとの親和性

    最も大切と言えるのが安全なデータ型の提供です。例えばデータの入れ物を(派生クラスではなく) DataSetやDataTable クラスを使用して引数や戻り値を定義したとします。そのデータ (オブジェクト) を処理する側にとっては、DataSet として送られてくるデータは、ProductCatalog テーブルの内容なのか UserObject テーブルの内容なのかは開発時にはわかりません(実行時バインディング)。ところが DataSet や DataTable 派生クラスを使用すると、DataSet 内にどんな DataTable が存在し、その DataTable にはどんな名前でどんなデータ型のカラム (列) が存在するのかが開発時に明確になります(事前バインディング)。

    例えば、3-4-1 もう一度データセットの作成 で実験をしたWebフォームにラベルコントロールを配置し、Page_Load イベントプロシージャに以下のコードを入力します。

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    

        ' ページを初期化する ユーザー コードをここに挿入します。         SqlDataAdapter1.Fill(DataSet11)         Dim dr As DataSet1.CatalogProductsRow         dr = DataSet11.CatalogProducts.Item(0)         Label1.Text = dr.ProductName     End Sub

事前バインディングなのでインテリセンスが機能することが確認できます。CatalogProductsRow 型のオブジェクトである dr にはテーブルの列名と同じ名前のプロパティが定義されていることがインテリセンスで確認できます。

![図](images/Cc707305.cs3-5(ja-jp,MSDN.10).jpg)

**図** **3-5 DataRow** **派生クラスのプロパティ**

再利用性を考慮してプログラミングを行うのであれば、可能な限りこの型指定された DataSet(DataSet 派生クラス) を使用して引数や戻り値を定義します。これにより、予期せぬデータのやり取りが行われる可能性が無くなり、安全性が向上します。

**注意**: ここまでのコードを実行する場合、ローカルコンピュータ上の ASPNET アカウントが SQL Server 上の Fabrikam データベースにアクセスする権限を持っている必要があります。これは、サーバーエクスプローラからテーブルをドロップして DataAdapter を作成すると、Windows 認証を使用して SQL Server にアクセスするためです。

3-5 XML ファイルからのカタログインポート

DataSet と XML との親和性も解説しておきます。ただし、DataSet は本当に簡単に XML データを扱うことができます。ここでは、それだけではつまらないので、XML Web サービスでの使用例と合わせて解説いたします。XML Web サービス に関してはご存知の方も多いと思うので、簡単な解説にとどめます。

3-5-1 XML Web サービス概要

異なるコンピュータに存在するオブジェクト間のデータのやり取りに XML Web サービスを使用することができます。XML Web サービスはマイクロソフト独自の呼び名ですが、今や標準技術となった Web サービスのことを指します。つまり、「オブジェクト間のデータのやり取り」 というのは 「サービス間のデータのやり取り」 のことであり、ここで言う 「サービス」 とは Web サービスを提供/利用する全てのシステムを指します。つまり、Windows 上で実行されているサービスでなくても、Web サービスさえ利用できるのであれば Windows 上のサービスとデータ交換が可能になる訳です。

3-5-2 XML Web サービスの作成

Visual Studio .NET を使用すれば XML Web サービスの作成はいたって簡単です。以下の 2 点さえ覚えておけば XML Web サービスを作成できます。

  • プロジェクトを作成する際〔ASP.NET Web サービス〕テンプレートを選択する。

  • WebService 属性 (<WebService>) を使用して定義されているクラス内に、WebMethod属性 (<WebMethod>) を使用したメソッドを定義する。

    後はこのプロジェクトをビルドすれば、.NET Framework が XML Web サービスとして必要な機能を全て提供してくれます。以下のコードはサンプルアプリケーションで使用する Supplier プロジェクト内にある CatalogSvc.asmx のコードビハインドページです。

<WebService(Namespace:="http://Column.ASPNET-SQL.Sample/")< _ Public Class CatalogSvc Inherits System.Web.Services.WebService <WebMethod()< Public Function CatalogTransfer() As DsCatalogs DsCatalogs1.ReadXml("C:\FabrikamSetup\CatalogData.xml") Return DsCatalogs1 End Function End Class

WebService 属性の付いたクラスは Visual Studio .NET が自動生成しますので、コードを書いたのは WebMethod 属性の付いた CatalogTransfer メソッドだけです(Namespace の定義は既定値を変更しています)。

3-5-3 XML Web サービスの利用

提供されている Web サービスを利用する場合はどうでしょう。これも Visual Studio .NET を私用すれば、以下の 2 点だけ覚えておけば簡単に利用することができます。

  • Web 参照を追加する

  • 自動生成されたプロキシクラスを使用して Web サービスにアクセスする

Web 参照の追加には WSDL という標準化された XML ドキュメントを使用します。WSDL は簡単に言えばその Web サービスのインターフェイスの定義を XML で表現したものです。もっと簡単に表すと、その Web サービスはどんな名前のメソッドを提供していて、それぞれのメソッドにはどんな型の引数が定義されており、どんな型の戻り値を返すのか。という定義が書かれているドキュメントです。XML Web サービスの場合、Web サービスを定義したファイル (.asmx) への URL パスに 「wsdl」 というパラメータを付けてアクセスすれば、このドキュメントが自動生成されダウンロードされます。

https://localhost/Supplier/CatalogSvc.asmx?wsdl

サンプルアプリケーションでは Fabrikam プロジェクトで〔Web 参照の追加〕を行い、アドレスボックスに上記 URL パスを入力して、Web 参照を追加しています。

図

3-6 Web 参照の追加

1 つ目のポイントである Web 参照を追加すると、自動的に 2 つ目のポイントであるプロキシクラスが Visual Studio .NET によって生成されます。プロキシクラスは Web サービスを利用するクライアントの代わりとなって Web サービスにアクセスを行ってくれる便利なクラスです。開発者はこのクラスを使用すれば Web サービスを利用するために必要なプロトコル (例えば SOAP など) に関して何の指示も行う必要はありません。全てプロキシクラスと .NET Framework が処理してくれます。

このプロキシクラスはソリューションエクスプローラを使用して〔Web References〕-〔localhost〕-〔Reference.map〕の配下に Reference.vb として存在します。このプロキシクラスのコードで以下の点を確認します。

  • Namespace として localhost が定義されている

  • CatalogSvc という XML Web サービスと同じ名前のクラスが定義されている

  • CatalogTransfer という XML Web サービスと同じ名前のメソッドが定義されている

また、もう 1 点確認すべきことは〔Web References〕-〔localhost〕の配下に DsCatalogs.xsd というスキーマファイルが作成されている点です。このファイルは Supplier プロジェクトにも存在します。XML Web サービスとして提供している CatalogTransfer メソッドの戻り値が DsCatalog 型のオブジェクトです。実は Web 参照を追加した際、WSDL からこの標準ではないデータ型の定義を読み取り、自動的にそのデータ型の定義をダウンロードしてくれています。これによって、Web サービス経由でやり取りする独自のデータ型のオブジェクトであっても、型指定されたデータ型となり、事前バインディングが可能になります。

図

3-7 プロキシクラスと DataSet 派生クラス

ここまで確認ができると、以下のコードの意味が理解できるはずです。Web サービスにアクセスするコードを書くといっても Visual Studio .NET を使用すれば、ローカルに存在するプロキシクラスにアクセスするコードを書くことになります。つまり、ローカルにある別のクラスのメソッドを呼び出す方法と全く同じコードなのです。以下のコードは /Fabrikam/Admin/CatalogImport.aspx の〔Web サービスからカタログデータをインポート〕ボタンのクリックイベントプロシージャです。

    Private Sub cmdGetCatalog_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdGetCatalog.Click
        Dim retData As Int32
        Try
            Dim svcSupplier As New localhost.CatalogSvc()
            DsCatalogs1.Merge(svcSupplier.CatalogTransfer())
            DataGrid1.DataSource = DsCatalogs1.Tables("CatalogProducts")
            DataGrid1.DataBind()
            retData = SqlDataAdapter1.Update(DsCatalogs1)
        Catch eException As SqlException
            Throw New ApplicationException("SQL Server のエラーです。インポートする前に削除するのを忘れていませんか?")
        Catch
            Throw
        End Try
        Label1.Text = retData.ToString() & " 件のデータをインポートしました。"
    End Sub

3-5-4 DataSet に XML データをロード

もう既に解説済みですが、DataSet に XML データを読み込むのはたった 1 行のコードです。

DsCatalogs1.ReadXml("C:\FabrikamSetup\CatalogData.xml")

上記コードは XML Web サービスとして作成した CatalogTransfer メソッドで使用されています。逆に DataSet の内容を XML ファイルとして保存するために WriteXML メソッドも提供されています。また、入力しようとしている XML データのスキーマが入力される型指定された DataSet のスキーマと異なる場合、XSLT を適用してスキーマを変換することも可能です。

3-5-5 XML データをテーブルに挿入

これまでの DataSet の解説はデータの読み取りに関しての使用例でした。ここでは XML Web サービス経由で入手した新しい商品データを CatalogProducts テーブルに挿入する例を解説します。と、言っても、DataSet を使用すればいたって簡単です。以下が先ほども確認した /Fabrikam/Admin/CatalogImport.aspx の〔Web サービスからカタログデータをインポート〕ボタンのクリックイベントプロシージャです。

    Private Sub cmdGetCatalog_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdGetCatalog.Click
        Dim retData As Int32
        Try
            Dim svcSupplier As New localhost.CatalogSvc()
            DsCatalogs1.Merge(svcSupplier.CatalogTransfer())
            DataGrid1.DataSource = DsCatalogs1.Tables("CatalogProducts")
            DataGrid1.DataBind()
            retData = SqlDataAdapter1.Update(DsCatalogs1)
        Catch eException As SqlException
            Throw New ApplicationException("SQL Server のエラーです。インポートする前に削除するのを忘れていませんか?")
        Catch
            Throw
        End Try
        Label1.Text = retData.ToString() & " 件のデータをインポートしました。"
    End Sub

XML Web サービスから受け取った新しい商品データを DataSet の Merge メソッドを使用して、データセットとして保存します。この時点ではメモリ上にあるデータセットとして保存されるだけです。その後、そのデータをデータグリッドコントロールを使用して表示し、SqlDataAdapter の Update メソッドを実行しています。この Update メソッドによって、新しい商品データがデータベース内に保存されます。

新しい商品データをデータベースに保存する場合、SQL Server に対して INSERT 文を発行する必要があります。実は DataAdapter を作成した時点でこの INSET 文は自動生成されています。そればかりか、UPDATE、DELETE 文に関しても同様です。SELECT 文を指定すれば、選択結果に対する変更を反映する際 (Update メソッドの実行) に必要な、INSERT、UPDATE、DELETE 文は自動生成されるのです。ただし、この機能は SELECT 文で選択されるデータが全て単一のテーブル内のものであることと、行を一意に識別することができる列を含んでいる場合にのみ正常に動作します。

寄り道、わき道、まわり道 (4) DataSetによる更新の注意点

DataAdapter の Update メソッドでデータソースの更新を行う場合、DataSet 内の全てのデータを更新するわけではありません。DataSet 内で更新されたデータだけがデータソースに反映されます。これを管理するため、各 DataRow オブジェクトには RowState プロパティがあり、どんな変更があったかを保存し、確認できるようになっています。 DataSet はデータソースと非接続で動作するデータの入れ物です。ということは、DataSet 内のデータが変更された後 Update メソッドが呼び出される前に、別のアプリケーションがデータベース内のデータを変更している可能性があります。いわゆる 「更新の競合」 が発生します。このため DataSet 内のデータを更新した場合でも DataSet はオリジナルのデータも保持しています。また、更新エラーが発生した場合の処理は時と場合によって異なるため、DataAdapter は RowUpdated イベントを提供します。このイベントプロシージャを作成し要求に合う処理を定義することが可能です。

3-5-6 動作確認

サンプルアプリケーションで新しい商品データをインポートするためには以下の手順を実行します。

  1. ユーザー名:yawara、パスワード:password として Fabrikam サイトにログオンします。ユーザー名:man でログインした場合ページヘッダに 〔管理者用〕 リンクが表示されません。

  2. CategoryPage.aspx ページが表示されます。現在のカテゴリは 「椅子」「机」「長椅子」 の 3 種類であることを確認し、〔管理者用〕リンクをクリックします。

  3. 管理者用ページ(/Fabrikam/Admin/CatalogImport.aspx) が表示されます。確認のため 〔CatalogProducts テーブルから花瓶データの取得〕ボタンをクリックします。

  4. データベースには花瓶の商品データが存在しないので、現在、花瓶のデータはテーブルに存在しませんというメッセージが表示されます。〔Web サービスからカタログデータをインポート〕ボタンをクリックします。5 件のデータをインポートしました。というメッセージが表示され、新しい商品データが表示されます。

  5. もう一度 Fabrikam サイトにアクセスし、カタログ情報を表示します。「花瓶」 カテゴリが追加されています。リンクをクリックするとインポートした 5 件の商品が表示されます。

既に商品データがある場合、〔Web サービスからカタログデータをインポート〕ボタンをクリックすると例外が発生します。2 回目以降実行する場合、4 の手順の前に〔CatalogProducts テーブルから花瓶データの削除〕をクリックし、データベース内の花瓶データを削除してください。

3-6 Microsoft Commerce Server 2002 では

Profile と同様 Catalog システムでもクラスライブラリがあれば ASP.NET の Web フォームの開発はかなり単純なものになります。しかし、このサンプルアプリケーションのクラスライブラリではあまりにも機能が少なすぎます。それは 3-1-1 商品カタログとは で解説しました。Microsoft Commerce Server 2002 を使用した場合、数多くの機能を提供され、データ構造の変更も GUI (管理ツール) で対応でき最大限の柔軟性を提供してくれます。

今回のサンプルアプリケーションで商品データに新しい項目 (例えば 「商品のサイズ」 など)が追加された場合、DataSet (正確には DataSet 派生クラス) の定義を変更する必要があります。定義の変更自体は Visual Studio .NET を使用すればデザイナ画面のデータセットビューで簡単にできます。サーバーエクスプローラでテーブルをドロップするだけで DataSet の定義を作成することも可能です。ただし、データベース上のテーブルの列名を変更したり、列のデータ型を変更した場合は注意が必要です。DataRow 派生クラスには列名とそのデータ型のプロパティが存在していたことを忘れないでください。当然、DataSet 派生クラスを更新すればこれらのプロパティの定義は更新されますが、これらのプロパティを使用しているコードまでは更新できません。Microsoft Commerce Server 2002 でも今回のサンプルアプリケーションでも同様ですが、クラスライブラリを利用するコードを書く前であれば簡単ですが、既に出来上がったアプリケーションに対してデータベースのスキーマを変更するのはかなりのリスクがあります。アプリケーションの設計段階おいてデータベース設計は非常に重要であることは間違いありません。ただ、 Microsoft Commerce Server 2002 にはソリューションサイトというアプリケーションのテンプレートがあるので、この作業を大幅に短縮してくれます。

また、カタログデータのインポートの機能を紹介しましたが、実際はサンプルのような簡単なものではありません。インポートするカタログデータが XML である保障はありませんし、XML データであったとしてもスキーマが異なることのほうが多いはずです。スキーマの問題は DataSet を使用すれば XSLT で変換するコードは簡単に作成できますが、インポートする相手が多数あったり、頻繁に変更される場合は、保守に相当のコストが発生することが予想されます。また、インポートの際の前手順 (認証やインポートする商品のリクエストなど) が必要な場合も考えられます。これらの問題は Microsoft BizTalk Server を使用することで非常に効率的に対応することが可能です。

CS2000.jpg

Fumio Mizobata: 東京を中心に .NET Framework ならびに .NET Enterprise Servers のトレーニングを開発、実施している。その多忙な業務の中、日々夜の街に吸い込まれ、そこでも大活躍しているようだが、実は大阪在住で2児の父親でもある。できるだけ多くの技術者のお役に立てる技術を伝達することに喜びを感じているので、トレーナーという職業はまさに天職である。