次の方法で共有


データ層コンポーネントの設計と層間のデータの受け渡し

Patterns and Practices ホーム

関連リンク

Patterns and Practices インデックス

.NET アーキテクチャ センター

.NET のアプリケーション アーキテクチャ : アプリケーションとサービスの設計

Angela Crocker, Andy Olsen, Edward Jezierski
Microsoft Corporation

August 2002
日本語版最終更新日 2004 年 4 月 5 日

対象 :
    Microsoft® .NET アプリケーション

概要 : Microsoft .NET アプリケーションにデータを公開する最適な方法、および分散アプリケーションの層間でのデータ受け渡しに効果的な方針を実装する方法を説明します。

ダウンロード

データ層コンポーネントの設計と層間のデータの受け渡し (Designing Data Tier Components and Passing Data Through Tiers)」 (英語) を .pdf 形式でダウンロードします。

目次

はじめに
ビジネス エンティティへのリレーショナル データのマッピング
データ アクセス ロジック コンポーネントの実装
ビジネス エンティティの実装
トランザクション
検証
例外管理
承認とセキュリティ
配置
付録
共同執筆者

はじめに

分散アプリケーションを設計するときは、アプリケーションに関連付けられるビジネス データにアクセスする方法とそのデータを表現する方法を決定する必要があります。 本書では、アプリケーションの各層間でそのデータの公開、保存、受け渡しを行う最も適切な方法を選択するのに役立つガイダンスを示します。

図 1 は、分散アプリケーションの一般的な層を表しています。 本書では、ビジネス データとそのデータを使用するビジネス プロセスを区別しています。ビジネス プロセス層については、説明が必要になった場合にのみ説明されています。 同様に、プレゼンテーション層については、Microsoft® ASP.NET Web ページでビジネス データを公開する方法など、データを表現する方法に直接影響がある場合にのみ説明されています。 図 1 では、データ アクセス ロジック コンポーネントとビジネス エンティティ コンポーネントという新しい 2 つの用語が導入されています。 これらの用語は、本書の後半で説明されています。

Dd297667.f01boa01(ja-jp,MSDN.10).gif

図 1. 分散アプリケーション内でのデータへのアクセスと表現

ほとんどのアプリケーションは、リレーショナル データベースにデータを格納します。 データを格納する場合に他の選択肢もありますが、本書では、.NET アプリケーションがリレーショナル データベースと対話する方法を中心に説明しています。フラット ファイルやリレーショナル データベース以外のデータベースなど、他のデータ ストアで保持されているデータを操作する方法については、特に説明していません。

本書では、データの保存ロジックとデータ自体が明確に区別されています。 データ自体とデータの保存ロジックを区別するのには、以下の理由があります。

  • 個別のデータ保存コンポーネントにより、アプリケーションとデータベースの依存関係を分離できるため。ここでいうデータベース依存関係とは、データ ソース名、接続情報、フィールド名などを指します。

  • 現在のアプリケーションの多くが、XML Web サービスや Microsoft メッセージ キュー (別名 MSMQ) など、メッセージに基づく疎結合テクノロジを使用するため。 一般的に、このようなアプリケーションは、オブジェクトを渡すのではなく、ビジネス ドキュメントを渡すことによって通信します。

       XML Web サービスの概論については、MSDN® Magazine 2002 年 3 月号の記事「.NET Web Services: Web Methods Make it Easy to Publish Your App's Interface over the Internet」 (英語) を参照してください。 メッセージ キューの詳細については、「メッセージ キューの概要」を参照してください。

本書では、データ保存ロジックとデータ自体を区別できるように、以下の 2 種類の異なるコンポーネントが提案されています。

  • データ アクセス ロジック コンポーネント。 データ アクセス ロジック コンポーネントにより、データベースからデータが取得され、エンティティ データとしてデータベースに保存されます。 また、このコンポーネントには、データに関連する操作に必要なビジネス ロジックも含まれています。
  • ビジネス エンティティ コンポーネント。 データは、製品や注文など、実社会のビジネス エンティティを表現するために使用されます。 アプリケーションでこのようなビジネス エンティティ (たとえば、XML やデータセット、またはオブジェクト指向のカスタム クラス) を表す方法は数多くあります。表現方法は、アプリケーションの物理設計および論理設計上の制約によって異なります。 設計のオプションについては、本書の後半で詳しく調べます。

データ アクセス ロジック コンポーネント

データ アクセス ロジック コンポーネントにより、呼び出し側の代って、データベース上で以下の作業を行うメソッドが提供されます。

  • データベースにレコードを作成する
  • データベース内のレコードを読み取り、ビジネス エンティティ データを呼び出し側に返す
  • 呼び出し側が提供した、修正済みのビジネス エンティティ データを使用して、データベース内のレコードを更新する
  • データベース内のレコードを削除する

上記の作業を行うメソッドは、多くの場合、"CRUD" メソッドと呼ばれます。"CRUD" とは、各作業を英語で表し、先頭文字だけを連結した頭字語です。

また、データ アクセス ロジック コンポーネントには、データベースに対してビジネス ロジックを実装するためのメソッドもあります。 たとえば、データ アクセス ロジック コンポーネントには、今月最も売れている製品をカタログで探すメソッドが含まれていたりします。

一般的に、データ アクセス ロジック コンポーネントでは、1 つのデータベースにアクセスし、そのデータベース内の 1 つのテーブルまたは関連テーブルのグループのデータ関連の操作をカプセル化されます。 たとえば、あるデータ アクセス ロジック コンポーネントをデータベース内の Customer テーブルと Address テーブルに対応するように定義することもあれば、別のデータ アクセス ロジック コンポーネントを Orders テーブルと OrderDetails テーブルに対応するように定義することもあります。 データ アクセス ロジック コンポーネントをデータベースのテーブルにマッピングするための設計上の決定については、本書の後半で説明されています。

ビジネス エンティティの表現

各データ アクセス ロジック コンポーネントにより、特定の種類のビジネス エンティティが処理されます。 たとえば、Customer というデータ アクセス ロジック コンポーネントにより Customer ビジネス エンティティが処理されます。 ビジネス エンティティを表現するにはざまざまな方法がありますが、どのような表現方法をとるかは次のような要因によって異なります。

  • Microsoft Windows® フォーム内または ASP .NET ページ上のコントロールにビジネス エンティティ データを連結する必要があるかどうか。
  • ビジネス エンティティ データで並べ替え操作や検索操作を実行する必要があるかどうか。
  • アプリケーションがビジネス エンティティを 1 つずつ処理するか、または、通常、一連のビジネス エンティティを処理するかどうか。
  • アプリケーションをローカルに配置するか、リモートに配置するか。
  • XML Web サービスでビジネス エンティティを使用するかどうか。
  • パフォーマンス、スケーラビリティ、管理の容易性、プログラミングの利便性など、機能以外の要件がどの程度重要であるか。

本書では、以下の実装オプションの長所と短所が説明されています。

  • XML。 XML 文字列または XML DOM (ドキュメント オブジェクト モデル) オブジェクトを使用して、ビジネス エンティティのデータを表します。 XML は、オープンで柔軟性のあるデータ表現形式です。この表現形式は、さまざまな種類のアプリケーションの統合に使用できます。

  • データセット。 データセットは、リレーショナル データベースまたは XML ドキュメントから取得される、テーブルのメモリ内キャッシュです。 データ アクセス ロジック コンポーネントでは、データベースから取得したビジネス エンティティ データを表現するためにデータセットを使用でき、アプリケーション内でそのデータセットを使用できます。 データセットの概要については、「.NET データ アクセス アーキテクチャ ガイド」 の「ADO.NET の紹介」を参照してください。

  • 型指定されたデータセット。 型指定されたデータセットは、ADO.NET DataSet クラスから継承されるクラスで、データセット内のテーブルや列にアクセスするために、厳密に型指定されたメソッド、イベント、およびプロパティを提供します。

  • ビジネス エンティティ コンポーネント。 これは、各種ビジネス エンティティを表すためのカスタム クラスです。 ビジネス エンティティ データを保持するフィールドを定義し、このデータをクライアント アプリケーションに公開するプロパティを定義します。 単純なビジネス ロジックをカプセル化するメソッドを定義して、クラスで定義されたフィールドを使用します。 このオプションでは、CRUD メソッドが元になるデータ アクセス ロジック コンポーネントに対するパススルー メソッドとして実装されません。クライアント アプリケーションは、データ アクセス ロジック コンポーネントと直接通信して、CRUD 操作を実行することになります。

  • CRUD 操作を備えたビジネス エンティティ コンポーネント。 上記のようにカスタム エンティティ クラスを定義し、このビジネス エンティティに関連付けられた元になるデータ アクセス ロジック コンポーネントを呼び出す CRUD メソッドを実装します。

       よりオブジェクト指向的な方法でデータを使った作業を行う場合は、共通言語ランタイムのリフレクション機能に基づくオブジェクト保存層を定義する代替アプローチも使用できます。 オブジェクトのプロパティを読み取るためにリフレクションを使用するフレームワークを作成し、マッピング ファイルを使用して、オブジェクトとテーブル間のマッピングを記述できます。 ただし、このようなアプローチを効果的に実装するには、インフラストラクチャ コードに対して多くの作業が必要になります。 ISV やソリューション プロバイダにとってはこのような作業に価値がある場合もありますが、大多数の組織ではあまり意味がありません。このアプローチについては、この資料では説明しません。

技術的な考慮事項

図 2 には、データ アクセス ロジック コンポーネントとビジネス エンティティの実装方針に影響を与える技術的な考慮事項の一部が示されています。 本書では、これらの技術的な考慮事項それぞれに対処し、推奨事項を提案します。

Dd297667.f01boa02(ja-jp,MSDN.10).gif

図 2. データ アクセス ロジック コンポーネントとビジネス エンティティの設計に影響を与える技術的な考慮事項

ビジネス エンティティへのリレーショナル データのマッピング

一般的に、データベースには多くのテーブルが含まれており、これらのテーブルには主キーや外部キーによって実装されるリレーションシップも含まれています。 このようなデータを .NET アプリケーションで表現するためのビジネス エンティティを定義するときは、これらのテーブルをビジネス エンティティにマップする方法を決定する必要があります。

図 3 で示すような架空の小売業者のデータベースを考えてみましょう。

Dd297667.f01boa03(ja-jp,MSDN.10).gif

図 3. 架空のリレーショナル データベース内のテーブルのリレーションシップ

次の表には、例として使用しているデータベース内のリレーションの種類がまとめられています。

リレーションシップの種類 説明
一対多 Customer : Address


Customer : Order
顧客には多くの住所 (送付先住所、請求先住所、連絡先住所など) を指定できます。

1 人の顧客は複数の注文を行えます。
多対多 Order : Product 注文には多くの製品を含めることができます。各製品は、OrderDetails テーブル内の個別の行で表されます。 同様に、1 つの製品は多数の注文で取り扱われることがあります。

データベース内の情報をモデル化するビジネス エンティティを定義するときは、その情報をアプリケーション内でどのように使用するかを検討します。 テーブルごとにビジネス エンティティを個別に定義するのではなく、アプリケーションの機能をカプセル化する中核となるビジネス エンティティを特定します。

架空の小売業者のアプリケーションでの典型的な操作は、以下のとおりです。

  • 顧客に関する情報 (住所など) を入手 (または更新) する
  • 顧客の注文の一覧を入手する
  • 特別な注文の注文明細の一覧を入手する
  • 新しい注文書を発行する
  • 商品または商品群に関する情報を入手 (または更新) する

このようなアプリケーション要件を満たすために、アプリケーションが処理する 3 つの論理的なビジネス エンティティ (Customer、Order、および Product) があります。 ビジネス エンティティごとに、個別のデータ アクセス ロジック コンポーネントを以下のように定義します。

  • Customer データ アクセス ロジック コンポーネント。 このクラスにより、Customer テーブルと Address テーブルのデータを取得および変更するためのサービスが提供されます。
  • Order データ アクセス ロジック コンポーネント。 このクラスにより、Order テーブルと OrderDetails テーブルのデータを取得および変更するためのサービスが提供されます。
  • Product データ アクセス ロジック コンポーネント。 このクラスにより、Product テーブルのデータを取得および変更するためのサービスが提供されます。

図 4 では、データ アクセス ロジック コンポーネントと、これらのコンポーネントがデータベース内で表現するテーブルとの間のリレーションシップが示されています。

Dd297667.f01boa04(ja-jp,MSDN.10).gif

図 4. リレーショナル データを .NET アプリケーションに公開するためのデータ アクセス ロジック コンポーネントの定義

データ アクセス ロジック コンポーネントの実装方法については、本書の「データ アクセス ロジック コンポーネントの実装」を参照してください。

リレーショナル データをビジネス エンティティにマッピングする場合の推奨事項

リレーショナル データをビジネス エンティティにマッピングする場合は、以下の推奨事項を考慮してください。

  • テーブルごとにビジネス エンティティを個別に定義するのではなく、アプリケーションの論理的なビジネス エンティティを分析し、モデル化するために時間を割きます。 アプリケーションの機能方法をモデル化する方法の 1 つとして、UML (Unified Modeling Language) を使用します。 UML とは、オブジェクト指向のアプリケーションのオブジェクトをモデル化したり、自動化されたプロセス、ユーザー操作、関連性をオブジェクトが表現する方法に関する情報を得るための公式の設計表記法です。 詳細については、「アプリケーションとデータのモデリング」を参照してください。
  • データベース内の多対多のテーブルを表す個別のビジネス エンティティを定義しないでください。このようなリレーションシップは、データ アクセス ロジック コンポーネントに実装されるメソッドによって公開できます。 たとえば、上記の例の OrderDetails テーブルは、個別のビジネス エンティティにマッピングされません。代わりに、Orders データ アクセス ロジック コンポーネントにより、OrderDetails テーブルがカプセル化され、Order テーブルと Product テーブル間の多対多のリレーションシップが実現されます。
  • 特定の種類のビジネス エンティティを返すメソッドがある場合は、その種類のデータ アクセス ロジック コンポーネントにこのようなメソッドを配置します。 たとえば、ある顧客のすべての注文を取得する場合、戻り値の型は Order 型になるので、Order データ アクセス ロジック コンポーネントにその関数を実装します。 反対に、特定の製品を注文したすべての顧客を取得する場合は、Customer データ アクセス ロジック コンポーネントにその関数を実装します。
  • 一般的に、データ アクセス ロジック コンポーネントは、1 つのデータ ソースからのデータにアクセスします。 複数のデータ ソースからのデータの集約が必要な場合、各データ ソースにアクセスする個別のデータ アクセス ロジック コンポーネントを定義することをお勧めします。集約を実行できるより高度なビジネス プロセス コンポーネントから、このような個別のデータ アクセス ロジック コンポーネントを呼び出せます。 これには、以下の 2 つの理由があります。
    • トランザクション管理がビジネス プロセス コンポーネントで集中管理され、データ アクセス ロジック コンポーネントで明示的に制御する必要がなくなります。 1 つのデータ アクセス ロジック コンポーネントから複数のデータ ソースにアクセスすると、このようなデータ アクセス ロジック コンポーネントをトランザクションのルートにする必要があります。その結果、データの読み取りのみを行う関数に新たなオーバーヘッドが生じます。
    • 通常、アプリケーションのすべての領域で集約が必要になるわけではありません。データへのアクセスを分離することにより、スタンドアロン型にすることも、必要に応じて集約に含めることもできます。

データ アクセス ロジック コンポーネントの実装

データ アクセス ロジック コンポーネントは状態なしのクラスです。つまり、交換されるすべてのメッセージは、他のメッセージとは切り離して解釈できます。 呼び出しと次の呼び出しの間では、状態が保持されません。 データ アクセス ロジック コンポーネントにより、1 つのデータベースの 1 つ以上の関連テーブルにアクセスするためのメソッドが提供されます。また、データベースを行分割したような場合は複数のデータベースの 1 つ以上の関連テーブルにアクセスするためのメソッドも提供されます。 一般的に、データ アクセス ロジック コンポーネント内のメソッドは、ストアド プロシージャを呼び出して、操作を実行します。

データ アクセス ロジック コンポーネントの主な目的の 1 つは、データベースの呼び出しと形式の特異性を、呼び出し側アプリケーションに意識させないことです。 データ アクセス ロジック コンポーネントにより、カプセル化されたデータ アクセス サービスが呼び出し側アプリケーションに提供されます。 特に、データ アクセス ロジック コンポーネントは、以下の実装の詳細を処理します。

  • ロック手法の管理とカプセル化
  • セキュリティと承認に関する問題の適切な処理
  • トランザクションに関する問題の適切な処理
  • データのページングの実行
  • データ依存のルーティングの実行 (必要に応じて)
  • 非トランザクション データのクエリのキャッシング方針の実装 (適切な場合)
  • データ ストリーミングとデータのシリアル化の実行

このような問題の一部が、このセクションの後半で詳しく説明されます。

データ アクセス ロジック コンポーネントのアプリケーション シナリオ

図 5 には、Windows フォーム アプリケーション、ASP.NET アプリケーション、XML Web サービス、ビジネス プロセスなど、さまざまな種類のアプリケーションからデータ アクセス ロジック コンポーネントを呼び出す方法が示されています。 アプリケーションの配置方法によって、このような呼び出しをローカルまたはリモートに実行できます。

クリックすると拡大表示されます

図 5. データ アクセス ロジック コンポーネントのアプリケーション シナリオ (クリックすると拡大表示されます)

データ アクセス ロジック コンポーネント クラスの実装

データ アクセス ロジック コンポーネントは、ADO.NET を使用して、SQL ステートメントを実行したり、ストアド プロシージャを呼び出します。 データ アクセス ロジック コンポーネント クラスの例については、付録の「データ アクセス ロジック コンポーネント クラスを定義する方法」を参照してください。

アプリケーションに複数のデータ アクセス ロジック コンポーネントが含まれている場合、データ アクセス ヘルパ コンポーネントを使用することにより、データ アクセス ロジック クラスの実装を簡略化できます。 このようなコンポーネントは、データベース接続の管理、SQL コマンドの実行、パラメータのキャッシュに役立ちます。 データ アクセス ロジック コンポーネントが、特定のビジネス データにアクセスするのに必要なロジックをカプセル化するのに対して、データ アクセス ヘルパ コンポーネントは、データ アクセス API の開発およびデータ接続の構成を集中管理します。その結果、コードの重複を削減するのに役立ちます。 Microsoft は、Data Access Application Block for .NET (英語) を提供しています。これは、Microsoft SQL Server™ デー タベースの使用時に汎用のデータ アクセス ヘルパ コンポーネントとして使用できます。 図 6 では、データ アクセス ヘルパ コンポーネントを使用してデータ アクセス ロジック コンポーネントの実装を支援する方法が示されています。

Dd297667.f01boa06(ja-jp,MSDN.10).gif

図 6. データ アクセス ヘルパ コンポーネントを使用したデータ アクセス ロジック コンポーネントの実装

すべてのデータ アクセス ロジック コンポーネントに共通するユーティリティ関数がある場合、データ アクセス ロジック コンポーネントの基本クラスを定義し、その基本クラスから継承や拡張を行うことができます。

さまざまな種類のクライアントにとって一貫性のあるインターフェイスを提供するようにデータ アクセス ロジック コンポーネント クラスを設計します。 現在および今後考えられるビジネス プロセス層の実装条件に対応するようにデータ アクセス ロジック コンポーネントを設計すると、実装する必要のある追加のインターフェイス、ファサード、またはマッピング層を削減できます。

さまざまなビジネス プロセスやアプリケーションをサポートするには、データ アクセス ロジック コンポーネント メソッド間のデータの受け渡しを行う以下の技法を検討します。

  • データ アクセス ロジック コンポーネント内のメソッドへのビジネス エンティティ データの受け渡し。 複数の異なる形式 (一連のスカラ値、XML 文字列、データセット、またはカスタム ビジネス エンティティ コンポーネント) でデータを渡すことができます。
  • データ アクセス ロジック コンポーネント内のメソッドからのビジネス エンティティ データの返却。 複数の異なる形式で (出力パラメータのスカラ値、XML 文字列、データセット、カスタム ビジネス エンティティ コンポーネント、またはデータ リーダーとして) データを返すことができます。

以下のセクションでは、データ アクセス ロジック コンポーネント間でビジネス エンティティ データを受け渡すためのオプションを示し、さらに、各オプションの長所と短所を説明します。 この情報は、特定のアプリケーション シナリオに基づいた、確かな情報による選択を行うのに役立ちます。

スカラ値を入力および出力として渡す

このオプションの長所を以下に示します。

  • 抽象化。 呼び出し側はビジネス エンティティを定義するデータのみを把握している必要があります。ビジネス エンティティ固有の型または固有の構造体を把握する必要はありません。
  • シリアル化。 スカラ値はシリアル化をネイティブにサポートします。
  • メモリの有効利用。 スカラ値は実際に必要なデータのみを搬送します。
  • パフォーマンス。 スカラ値はインスタンス データを処理するときに、本書で説明する他のオプションよりも優れたパフォーマンスを提供します。

このオプションの短所を以下に示します。

  • 密結合と保守。 スキーマを変更すると、メソッド シグニチャを変更する必要があることがあります。これは、呼び出し側のコードに影響を与えます。
  • エンティティのコレクション。 複数のエンティティをデータ アクセス ロジック コンポーネントに保存または更新するには、個別にメソッドの呼び出しを行う必要があります。 これは、分散環境でのパフォーマンスに大きな影響を与えます。
  • オプティミスティック同時実行制御のサポート。 オプティミスティック同時実行制御をサポートするには、タイム スタンプ列をデータベース内で定義して、データの一部として含める必要があります。

XML 文字列を入力および出力として渡す

このオプションの長所を以下に示します。

  • 疎結合。 呼び出し側はビジネス エンティティを定義するデータ、およびビジネス エンティティにメタデータを提供するスキーマのみを把握する必要があります。
  • 統合。 XML を使用することにより、さまざまな方法 (たとえば、.NET アプリケーション、BizTalk オーケストレーション ルール、サード パーティのビジネス ルール エンジンなど) で実装された呼び出し側がサポートされます。
  • ビジネス エンティティのコレクション。 XML 文字列には、複数のビジネス エンティティのデータを含めることができます。
  • シリアル化。 文字列はシリアル化をネイティブにサポートします。

このオプションの短所を以下に示します。

  • XML 文字列の再解析にかかる労力。 XML 文字列は受け取り側で再解析する必要があります。 大きな XML 文字列ではパフォーマンスのオーバーヘッドが発生します。
  • 非効率的なメモリの使用。 XML 文字列は冗長なことがあります。これは、多量のデータを渡す必要がある場合にメモリの使用が非効率的になることがあります。
  • オプティミスティック同時実行制御のサポート。 オプティミスティック同時実行制御をサポートするには、タイム スタンプ列をデータベース内で定義して、XML データの一部として含める必要があります。

データセットを入力および出力として渡す

このオプションの長所を以下に示します。

  • 本来備わっている機能。 データセットにより、(データ アダプタと共に) オプティミスティック同時実行制御を処理する組み込み機能が提供され、複雑なデータ構造をサポートされます。 さらに、型指定されたデータセットによりデータ検証がサポートされます。
  • ビジネス エンティティのコレクション。 データセットは、セットや複雑なリレーションシップを処理するように設計されています。そのため、カスタム コードを記述して、このような機能を実装する必要はありません。
  • 保守。 スキーマを変更しても、メソッド シグニチャには影響しません。 ただし、型指定されたデータセットを使用していて、アセンブリに厳密な名前が付いている場合、データ アクセス ロジック コンポーネント クラスは、新しいバージョンに再コンパイルするか、グローバル アセンブリ キャッシュ内部の発行者ポリシーを使用するか、または構成ファイル内の 要素を定義する必要があります。 ランタイムがアセンブリを検索する方法の詳細については、「ランタイムがアセンブリを検索する方法」を参照してください。
  • シリアル化。 データセットは、XML のシリアル化をネイティブにサポートし、層をまたがってシリアル化できます。

このオプションの短所を以下に示します。

  • パフォーマンス。 データセットのインスタンス作成とマーシャリングにより、ランタイムのオーバーヘッドが発生します。
  • 1 つのビジネス エンティティの表現。 データセットは、データのセットを処理するように設計されています。 アプリケーションが主にインスタンス データを使用して機能している場合は、パフォーマンスのオーバーヘッドが発生しないので、スカラ値またはカスタム エンティティの方が適切なアプローチになります。

カスタム ビジネス エンティティ コンポーネントを入力および出力として渡す

このオプションの長所を以下に示します。

  • 保守。 スキーマを変更しても、データ アクセス ロジック コンポーネントのメソッド シグニチャに影響しません。 ただし、ビジネス エンティティ コンポーネントが厳密な名前の付いたアセンブリに保持されている場合、型指定されたデータセットと同様の問題が発生します。
  • ビジネス エンティティのコレクション。 カスタム ビジネス エンティティ コンポーネントの配列またはコレクションを、メソッド間で受け渡しできます。

このオプションの短所を以下に示します。

  • オプティミスティック同時実行制御のサポート。 オプティミスティック同時実行制御を簡単にサポートするには、タイム スタンプ列をデータベース内で定義して、インスタンス データの一部として含める必要があります。
  • 制限付きの統合。 データ アクセス ロジック コンポーネントへの入力としてカスタム ビジネス エンティティ コンポーネントを使用する場合、呼び出し側はビジネス エンティティの型を認識しておく必要があります。これは、.NET を使用していない呼び出し側の統合を制限する可能性があります。 ただし、呼び出し側がデータ アクセス ロジック コンポーネントからの出力としてカスタム ビジネス エンティティ コンポーネントを使用する場合、この問題が統合を制限することにはなりません。 たとえば、Web メソッドは、データ アクセス ロジック コンポーネントから返されたカスタム ビジネス エンティティ コンポーネントを返すことができます。また、そのビジネス エンティティ コンポーネントは、XML シリアル化を使用して XML に自動的にシリアル化されます。

データ リーダーを出力として返す

このオプションの長所を以下に示します。

  • パフォーマンス。 データを迅速に表示する必要があり、プレゼンテーション層のコードを備えたデータ アクセス ロジック コンポーネントを配置できるときは、パフォーマンス上の利点があります。

このオプションの短所を以下に示します。

  • リモート処理。 リモート処理のシナリオでデータ リーダーを使用することはお勧めしません。それは、クライアント アプリケーションでデータベース接続が長期間にわたって開かれたままになる可能性があるためです。

データ アクセス ロジック コンポーネントに関連したストアド プロシージャの使用

ストアド プロシージャを使用して、データ アクセス ロジック コンポーネントでサポートされるデータ アクセス作業の多くを実行できます。

長所

  • 一般的に、ストアド プロシージャはパフォーマンスを向上します。これは、データベースがプロシージャで使用されるデータ アクセス プランを最適化し、後で再利用できるようにそのプランをキャッシュできるためです。
  • ストアド プロシージャは、データベース内部で個別にセキュリティで保護できます。 管理者は、クライアントに元になるテーブルへのアクセス許可を与えることなく、ストアド プロシージャを実行するアクセス許可を与えることができます。
  • ストアド プロシージャは結果的に保守が容易になる場合があります。これは、一般的に、ストアド プロシージャを変更することは、配置済みのコンポーネント内部にハード コードされた SQL ステートメントを変更するよりも簡単なためです。 ただし、ストアド プロシージャに装されるビジネス ロジックが増加すると、利点は減少します。
  • ストアド プロシージャは、元になるデータベース スキーマに新たなレベルの抽象化を追加します。 ストアド プロシージャのクライアントは、ストアド プロシージャの実装の詳細や元になるスキーマを意識する必要はありません。
  • ストアド プロシージャにより、ネットワーク トラフィックを削減できます。 アプリケーションは複数の SQL 要求を送信する必要がなく、SQL ステートメントを一括実行できます。

上記の長所にも関わらず、ストアド プロシージャの使用が勧められない、または不可能な場合もあります。

短所

  • 広範なビジネス ロジックや処理に関与するアプリケーションのロジックがすべてストアド プロシージャに実装されると、サーバーに過大な負荷をかけることがあります。 この種の処理の例には、データの転送、データ間の移動、データの変換、および集中的な計算操作があります。 このような処理は、ビジネス プロセスやデータ アクセス ロジック コンポーネントに移動する必要があります。これらのコンポーネントは、データベース サーバーよりもスケーラブルなリソースです。
  • すべてのビジネス ロジックをストアド プロシージャに含めません。 T-SQL のビジネス ロジックを変更する必要があるときは、アプリケーションの保守や敏捷性が問題になります。 たとえば、複数の RDBMS をサポートする ISV アプリケーションでは、システムごとに個別のストアド プロシージャを保守する必要はありません。
  • ストアド プロシージャの作成と保守は、多くの場合、すべての開発者が処理できるとは限らない特殊なスキルが必要になります。 このような状況は、プロジェクトの開発スケジュールのボトルネックになる場合があります。

データ アクセス ロジック コンポーネントでストアド プロシージャを使用する場合の推奨事項

データ アクセス ロジック コンポーネントに関連してストアド プロシージャを使用する場合、以下の推奨事項を考慮します。

  • ストアド プロシージャを公開します。 データ アクセス ロジック コンポーネントは、ストアド プロシージャ名、パラメータ、テーブル、フィールドなど、データベース スキーマの情報に対して公開される唯一のコンポーネントになる必要があります。 ビジネス エンティティの実装には、データベース スキーマの知識や依存関係の必要性をなくすべきです。

  • ストアド プロシージャをデータ アクセス ロジック コンポーネントに関連付けます。 各ストアド プロシージャは、1 つのデータ アクセス ロジック コンポーネントだけから呼び出されるべきなので、各ストアド プロシージャを、操作を所持するデータ アクセス ロジック コンポーネントに関連付ける必要があります。 たとえば、顧客が小売業者に発注するとします。 OrderInsert という名前で、データベースに注文を作成するストアド プロシージャを記述できます。 アプリケーションでは、Customer データ アクセス ロジック コンポーネントまたは Order データ アクセス ロジック コンポーネントのどちらからストアド プロシージャを呼び出すかを決定する必要があります。 注文に関するすべての処理を行うため、Order データ アクセス ロジック コンポーネントを選択することをお勧めします (Customer データ アクセス ロジック コンポーネントは、顧客の名前や住所などの顧客情報を処理します)。

  • ストアド プロシージャに名前を付けます。 使用するデータ アクセス ロジック コンポーネントのストアド プロシージャを定義するときは、関連するデータ アクセス ロジック コンポーネントを想起できるストアド プロシージャ名を選択します。 この命名規則により、ストアド プロシージャを呼び出すコンポーネントを簡単に特定でき、SQL Enterprise Manager 内部でストアド プロシージャを論理的にグループ化する方法が提供されます。 たとえば、Customer データ アクセス ロジック コンポーネントで使用するために、CustomerInsert、CustomerUpdate、CustomerGetByCustomerID、CustomerDelete などという名前のストアド プロシージャを記述するようにすれば、その後、アプリケーションのビジネス機能をサポートする CustomerGetAllInRegion のような、より具体的なストアド プロシージャを提供できます。

       ストアド プロシージャ名の先頭に sp_ を付けないでください。sp_ を付けることにより、パフォーマンスが低下します。 sp_ で始まるストアド プロシージャを呼び出すと、ストアド プロシージャがデータベース名で修飾されている場合でも、SQL Server は必ず最初に master データベースを確認します。

  • セキュリティの問題に対処します。 ユーザー入力に従ってクエリを動的に実行する場合、値を連結して文字列を作成するのではなく、パラメータを使用してください。 また、sp_execute を使用して作成した文字列を実行したり、sp_executesql パラメータのサポートを利用しない場合は、ストアド プロシージャ内で文字列連結を使用しないでください。

ロックと同時実行制御の管理

アプリケーションによっては、データベースのデータを更新するときに、"最新操作の優先" アプローチを採用するものがあります。 "最新操作の優先" アプローチでは、データベースが更新され、元のレコードと更新内容を比較する作業が行われず、レコードが最後に更新された後で、他のユーザーが行った変更により上書きされる可能性があります。 ただし、場合によっては、更新を行う前に、データを最初に読み取った後にそのデータが変更されたかどうかをアプリケーションが判断することが重要になることもあります。

データ アクセス ロジック コンポーネントにより、ロックや同時実行制御を管理するコードが実装されます。 ロックと同時実行制御を管理するには以下の 2 つの方法があります。

  • ペシミスティック同時実行制御。 行の更新を目的として行を読み取るユーザーが、データ ソース内の行のロックを確立します。 ユーザーがロックを開放するまで他のユーザーは行を変更できません。
  • オプティミスティック同時実行制御。 ユーザーが行を読み取るときにその行をロックしません。 その間、他のユーザーは自由にその行にアクセスできます。 アプリケーションは、ユーザーが行を更新するときに、その行を読み取った後に別のユーザーがその行を変更したかどうかを判断する必要があります。 既に変更されたレコードの更新を試みると、同時実行違反が発生します。

ペシミスティック同時実行制御の使用

ペシミスティック同時実行制御は、データの競合が深刻な環境、およびロックによるデータの保護にかかるコストが同時実行の競合が発生した場合にトランザクションをロールバックする際のコストよりも低い環境で主に使用されます。 ペシミスティック同時実行制御は、プログラムによるレコードの処理など、ロック時間が短いときに最適に実装されます。

ペシミスティック同時実行制御では、レコードが比較的長時間ロックされる可能性があるので、データベースへの永続的な接続が必要になります。また、ユーザーがデータを操作しているときにスケーラブルなオプションにはなりません。

オプティミスティック同時実行制御の使用

オプティミスティック同時実行制御は、データの競合が少ない環境、またはデータの読み取り専用アクセスが必要な環境に適しています。 オプティミスティック同時実行制御は、必要なロックの数を削減することにより、データベースのパフォーマンスを向上します。その結果、データベース サーバーの負荷が軽減されます。

オプティミスティック同時実行制御は、.NET で広く使用されており、長時間にわたってデータ行をロックすることができないモバイル アプリケーションや非接続のアプリケーションのニーズに対応しています。 また、レコードのロックを管理するには、データベース サーバーへの永続的な接続が必要になります。これは、非接続のアプリケーションでは不可能です。

オプティミスティック同時実行違反のテスト

オプティミスティック同時実行違反のテストを行うには、いくつかの方法があります。

  • 分散タイム スタンプを使用します。 違反の調整が不要な場合は、分散タイム スタンプが適しています。 データベース内の各テーブルにタイム スタンプ列またはバージョン列を追加します。 タイム スタンプ列は、テーブルのコンテンツのすべてのクエリで返されます。 更新を試みると、データベース内のタイム スタンプ値が、変更された行に含まれる元のタイム スタンプ値と比較されます。 これらの値が一致すると、更新が実行され、タイム スタンプ列が現在時刻で更新され、更新内容が反映されます。 値が一致しない場合は、オプティミスティック同時実行違反が発生します。

  • 元のデータ値のコピーを管理します。 データベースからデータをクエリするときに、元のデータ値のコピーを保持します。 データベースを更新するときは、データベース内の現在値が元の値と一致していることを確認します。

  • データセットでは、データベースを更新するときにデータ アダプタがオプティミスティック同時実行制御チェックに使用できる元のデータが保持されます。

  • 集中管理タイム スタンプを使用します。 すべてのテーブルのすべての行に対する更新をログに記録するために、データベースで集中管理されるタイム スタンプ テーブルを定義します。 たとえば、タイム スタンプ テーブルで "テーブル XYZ の ID 1234 の行が 2002 年 3 月 26 日午後 2 時 56 分に John によって更新されました" というように示すことができます。

    集中管理タイム スタンプは、チェックアウト シナリオ、および明確な所有者とロックやオーバーライドの管理が必要となる非接続のクライアント シナリオに適しています。 さらに、集中管理タイム スタンプは、必要に応じて監査上の利点をもたらします。

手作業によるオプティミスティック同時実行制御の実装

次の SQL クエリを考えてみましょう。


SELECT Column1, Column2, Column3 FROM Table1

Table1 のある行を更新するときにオプティミスティック同時実行違反のテストを行うには、次の UPDATE ステートメントを発行します。


UPDATE Table1 Set Column1 = @NewValueColumn1,
              Set Column2 = @NewValueColumn2,
              Set Column3 = @NewValueColumn3
WHERE Column1 = @OldValueColumn1 AND
      Column2 = @OldValueColumn2 AND
      Column3 = @OldValueColumn3

元の値がデータベース内の値と一致すると、更新が行われます。 値が変更されている場合は、WHERE 句により値が一致するとは認められないので、更新によって行は変更されません。 このような技法を応用して、WHERE 句を特定の列にのみ適用できます。その結果、特定のフィールドが最後にクエリされた後に更新されない限り、データは上書きされます。

   UPDATE ステートメントの WHERE 句で使用するために、常に、主キーなど、クエリの行を一意に識別する値を返します。 これにより、UPDATE ステートメントは確実に現在の行を更新します。

データ ソースの列で Null が許容される場合、ローカル テーブルの Null 参照とデータ ソースの Null 参照が一致することを確認するように WHERE 句を拡張する必要がある場合があります。 たとえば、次の UPDATE ステートメントでは、ローカル行の Null 参照がデータ ソースの Null 参照と一致していること、またはローカル行の値がデータ ソースの値に一致していることを検証します。


UPDATE Table1 Set Column1 = @NewColumn1Value
WHERE (@OldColumn1Value IS NULL AND Column1 IS NULL) OR Column1 =
@OldColumn1Value

データ アダプタとデータセットを使用したオプティミスティック同時実行制御の実装

DataAdapter.RowUpdated イベントを、上記の技法と組み合わせて、アプリケーションにオプティミスティック同時実行違反を通知できます。 RowUpdated イベントは、データセットからの変更済みの行の更新を試みるたびに発生します。 RowUpdated イベントを使用して、特別な処理コードを追加できます。このコードには、例外が発生する際の処理、カスタム エラー情報の追加、および再試行ロジックの追加を含めます。

RowUpdated イベント ハンドラは、RowUpdatedEventArgs オブジェクトを受け取ります。このオブジェクトには、テーブル内の変更済みの行の中で Update コマンドの影響を受けた行数を示す RecordsAffected プロパティがあります。 Update コマンドを設定して、オプティミスティック同時実行制御のテストを行う場合、オプティミスティック同時実行違反が発生すると、RecordsAffected プロパティは 0 になります。 RowUpdatedEventArgs.Status プロパティを設定して、続行方法を指定します。たとえば、このプロパティを UpdateStatus.SkipCurrentRow に設定すると、Update コマンドでの現在行の更新が省略されますが、他の行の更新は続行されます。 RowUpdated イベントの詳細については、「DataAdapter イベントの使用」を参照してください。

データ アダプタを使用して同時実行違反をテストする他の方法は、DataAdapter.ContinueUpdateOnError プロパティを True に設定してから Update メソッドを呼び出すことです。 更新が完了するときに、DataTable オブジェクトの GetErrors メソッドを呼び出して、エラーが発生した行を判断します。 その後、その行の RowError プロパティを使用して、特定のエラーの詳細を確認します。 行エラーの処理方法の詳細については、「行のエラー情報の追加と読み取り」を参照してください。

以下のコード サンプルでは、Customer データ アクセス ロジック コンポーネントで同時実行違反をチェックする方法を示しています。 この例では、クライアントがデータセットを取得して、データの変更を行い、その後、データ アクセス ロジック コンポーネントの UpdateCustomer メソッドにデータセットを渡すことを想定しています。 UpdateCustomer メソッドは、以下のストアド プロシージャを呼び出して、適切な顧客レコードを更新します。ストアド プロシージャは、顧客 ID および企業名がまだ変更されていない場合のみ、レコードを更新します。


CREATE PROCEDURE CustomerUpdate
{
  @CompanyName varchar(30),
  @oldCustomerID varchar(10),
  @oldCompanyName varchar(30)
}
AS
  UPDATE Customers Set CompanyName = @CompanyName
  WHERE CustomerID = @oldCustomerID AND CompanyName = @oldCompanyName
GO

以下のコード サンプルは、UpdateCustomer メソッドの内部で、データ アダプタの UpdateCommand プロパティを設定してオプティミスティック同時実行制御のテストを行い、その後、RowUpdated イベントを使用して、オプティミスティック同時実行違反のテストを行います。 アプリケーションでは、オプティミスティック同時実行違反が発生すると、更新が行われた行の RowError を設定することにより違反が示されます。 UPDATE コマンドの WHERE 句に渡されるパラメータ値は、データセット内の各列の Original 値にマップされることに注意してください。


// CustomerDALC クラスの UpdateCustomer メソッド
public void UpdateCustomer(DataSet dsCustomer)
{
  // Northwind データベースに接続します
  SqlConnection cnNorthwind = new SqlConnection(
    "Data source=localhost;Integrated security=SSPI;Initial
Catalog=northwind");

  // データ アダプタを作成して、Northwind の Customers テーブルにアクセスします
  SqlDataAdapter da = new SqlDataAdapter();

  // データ アダプタの UPDATE コマンドを設定して、"UpdateCustomer" ストアド プロシージャを呼び出します。
  da.UpdateCommand = new SqlCommand("CustomerUpdate", cnNorthwind);
  da.UpdateCommand.CommandType = CommandType.StoredProcedure;

  // データ アダプタの UPDATE コマンドに 2 つのパラメータを追加して、WHERE 句の情報を指定します
  // (オプティミスティック同時実行違反の確認を容易にします)
  da.UpdateCommand.Parameters.Add("@CompanyName", SqlDbType.NVarChar, 30,
"CompanyName");

  // WHERE 句の最初のパラメータとして CustomerID の original 値を指定します
  SqlParameter myParm = da.UpdateCommand.Parameters.Add(
                            "@oldCustomerID", SqlDbType.NChar, 5,
"CustomerID");
  myParm.SourceVersion = DataRowVersion.Original;

  // WHERE 句の 2 番目のパラメータとして CustomerName の original 値を指定します
  myParm = da.UpdateCommand.Parameters.Add(
                            "@oldCompanyName", SqlDbType.NVarChar, 30,
"CompanyName");
  myParm.SourceVersion = DataRowVersion.Original;

  // 行更新イベントのハンドラを追加します
  da.RowUpdated += new SqlRowUpdatedEventHandler(OnRowUpdated);

  // データベースを更新します
  da.Update(ds, "Customers");

  foreach (DataRow myRow in ds.Tables["Customers"].Rows)
  {
    if (myRow.HasErrors)
      Console.WriteLine(myRow[0] + " " + myRow.RowError);
  }
}

// 行更新イベントを処理するメソッド。 イベントを登録しても処理しない場合、
// SQL 例外がスローされます。
protected static void OnRowUpdated(object sender, SqlRowUpdatedEventArgs
args)
{
  if (args.RecordsAffected == 0)
  {
    args.Row.RowError = "Optimistic Concurrency Violation Encountered";
    args.Status = UpdateStatus.SkipCurrentRow;
  }
}

1 つの SQL Server ストアド プロシージャ内部で複数の SQL ステートメントを実行する場合、パフォーマンス上の理由から、SET NOCOUNT ON オプションの使用を選択することがあります。 このオプションにより、SQL Server が各ステートメントの完了後にクライアントにメッセージを返すことを防ぎ、ネットワーク トラフィックが削減されます。 ただし、上記のコード サンプルで例示したように、RecordsAffected プロパティを確認できません。 RecordsAffected プロパティは、常に -1 になります。 代替方法として、ストアド プロシージャの @@ROWCOUNT 関数を返します (または出力パラメータとしてこの関数を指定します)。@@ROWCOUNT は、ストアド プロシージャで最後に完了したステートメントのレコード数を含んでおり、SET NOCOUNT ON が使用されている場合でも更新されます。 その結果、ストアド プロシージャ内で実行された最後の SQL ステートメントが実際の UPDATE ステートメントで、@@ROWCOUNT を戻り値として指定する場合、アプリケーション コードを以下のように変更できます。


// データ アダプタの UPDATE コマンドに別のパラメータを追加して、戻り値を受け取ります。
// その戻り値に名前を付けることができます。
myParm = da.UpdateCommand.Parameters.Add("@RowCount", SqlDbType.Int);
myParm.Direction = ParameterDirection.ReturnValue;

// OnRowUpdated メソッドを変更して、RecordsAffected プロパティの代わりに
// このパラメータの値を確認します。
protected static void OnRowUpdated(object sender, SqlRowUpdatedEventArgs
args)
{
  if (args.Command.Parameters["@RowCount"].Value == 0)
  {
    args.Row.RowError = "Optimistic Concurrency Violation Encountered";
    args.Status = UpdateStatus.SkipCurrentRow;
  }
}

COM 相互運用性

データ アクセス ロジック コンポーネント クラスを COM クライアントから呼び出し可能にする場合、上記のガイドラインを使用してデータ アクセス ロジック コンポーネントを定義し、ラッパー コンポーネントを提供する方法をお勧めします。 ただし、COM クライアントがデータ アクセス ロジック コンポーネントにアクセスできるようにする場合、以下の推奨事項を考慮してください。

  • クラスとそのメンバをパブリックとして定義します。
  • 静的メンバの使用を避けます。
  • マネージ コードでイベント ソース インターフェイスを定義します。
  • パラメータを使用しないコンストラクタを用意します。
  • オーバーロードされたメソッドを使用しません。 代わりに別の名前の付いたメソッドを使用します。
  • 共通の操作を公開するインターフェイスを使用します。
  • 特別な COM 情報をクラスおよびメンバに提供する属性を使用します。
  • .NET コードからスローされた任意の例外に HRESULT 値を含めます。
  • メソッド シグニチャでオートメーション互換データ型を使用します。

COM 相互運用性の詳細については、「Microsoft .NET/COM の移行と相互運用性」 を参照してください。

ビジネス エンティティの実装

ビジネス エンティティには、以下の特性が備わっています。

  • ビジネス データおよび (一部の設計では) 関連する機能へのプログラムによるステートフルなアクセスを提供します。
  • 複雑なスキーマを持つデータからビルドできます。 通常、データは、データベース内の複数の関連テーブルから取得します。
  • ビジネス エンティティ データを、ビジネス プロセスの入出力パラメータの一部として渡すことができます。
  • エンティティの現在状態を保存するために、シリアル化できます。 たとえば、アプリケーションは、ローカル ディスク、アプリケーションがオフラインで動作している場合はデスクトップ データベース、またはメッセージ キュー メッセージにエンティティ データを格納する必要がある場合があります。
  • 直接データベースにアクセスすることはありません。 すべてのデータベース アクセスは、関連付けられたデータ アクセス ロジック コンポーネントによって提供されます。
  • どのような種類のトランザクションも開始しません。 トランザクションは、ビジネス エンティティを使用しているアプリケーションやビジネス プロセスによって開始されます。

本書で既に説明したように、データ中心のモデルからオブジェクト指向の表現に至るまで、アプリケーションでビジネス エンティティを表現するには以下のようなさまざまな方法があります。

  • XML
  • 汎用データセット
  • 型指定されたデータセット
  • カスタム ビジネス エンティティ コンポーネント
  • CRUD 操作を備えたカスタム ビジネス エンティティ コンポーネント

以下のセクションでは、上記の各形式でビジネス エンティティのデータを表現する方法について説明します。 特定の環境にとって適切なビジネス エンティティの表現方法を決定できるように、ビジネス エンティティの形式ごとにそれ以降の作業の実行方法について説明します。

  • ビジネス エンティティのコレクションの編成
  • ユーザー インターフェイス コントロールへのビジネス エンティティのデータ連結
  • ビジネス エンティティのデータのシリアル化
  • 層間でのビジネス エンティティ データの受け渡し

また、このセクションでは、パフォーマンス、効率性、スケーラビリティ、拡張性など、機能以外の要件の観点から各ビジネス エンティティの表現方法が適切であるか検討します。

XML としてのビジネス エンティティの表現

次の例では、単純なビジネス エンティティを XML として表現する方法を示しています。 このビジネス エンティティは、単一の製品で構成されています。


<?xml version="1.0"?>
<Product xmlns="urn:aUniqueNamespace">
  <ProductID>1</ProductID>
  <ProductName>Chai</ProductName>
  <QuantityPerUnit>10 boxes x 20 bags</QuantityPerUnit>
  <UnitPrice>18.00</UnitPrice>
  <UnitsInStock>39</UnitsInStock>
  <UnitsOnOrder>0</UnitsOnOrder>
  <ReorderLevel>10</ReorderLevel>
</Product>

詳細については、付録の「データのコレクションと階層を表現するために XML を使用する方法」を参照してください。

XML を使用してビジネス エンティティ データを表現するときは、以下のガイドラインを考慮してください。

  • XML ドキュメントには 1 つのビジネス エンティティまたはビジネス エンティティのコレクションのどちらを含める必要があるかを決定します。 上記の例では、1 つの Product ビジネス エンティティが表現されています。
  • 他の XML ドキュメントのコンテンツと名前が競合するのを回避するために、名前空間を使用して XML ドキュメントを一意に識別します。 上記の例では、urn:aUniqueNamespace という既定の名前空間を使用しています。
  • 要素と属性に適切な名前を選択します。 上記の例では、Product テーブルの列名を使用していますが、これは必要条件ではありません。 アプリケーションにとって意味のある名前を選択します。
  • 以下の方法のいずれかを使用して、ビジネス エンティティを XML 形式で取得します。
    • SQL Server 2000 を使用している場合、クエリまたはストアド プロシージャで FOR XML 句を使用できます。 パフォーマンスのテストでは、FOR XML を使用すると、ほんのわずかですがデータセットを返すよりも処理が速くなります。
    • データセットを取得し、XML ストリームとしてそのデータセットを変換または書き出します。 この方法では、データセットを作成する際のオーバーヘッドと、トランザクションを実行する場合には追加のオーバーヘッドが発生します。
    • 出力パラメータから、またはデータ リーダーを使用して、XML ドキュメントをビルドします。 データ リーダーは、データベースから複数の行を取得するのに最も高速な方法ですが、XML のビルドに関連する処理では、パフォーマンス上の利点が低下します。

詳細、およびパフォーマンスに関する考慮事項については、「Performance Comparison: Data Access Techniques」 (英語) を参照してください。

ビジネス エンティティを XML として表現した場合の長所を以下に示します。

  • 標準のサポート。 XML は、World Wide Web Consortium (W3C) 標準のデータ表現形式です。 この標準に関する詳細については、http://www.w3.org/xml Non-MS link (英語) を参照してください。
  • 柔軟性。 XML では、情報の階層およびコレクションを表現できます。 詳細については、付録の「データのコレクションと階層を表現するために XML を使用する方法」を参照してください。
  • 相互運用性。 XML は、プラットフォームに関係なく、外部団体や取引先との情報交換に理想的な選択肢です。 XML データが ASP.NET アプリケーションまたは Windows フォーム アプリケーションで使用される場合、XML データをデータセットに読み込み、データセットが提供するデータ連結のサポートを利用できます。

ビジネス エンティティを XML として表現した場合の短所を以下に示します。

  • 型の忠実度の保持。 XML では型の忠実度が保持されません。 ただし、単純なデータ型の指定に XSD スキーマを使用できます。
  • XML の検証。 XML を検証するには、コードを手作業で解析するか、XSD スキーマを使用できます。 どちらの方法も比較的時間がかかります。 XSD スキーマを使用して XML を検証する方法の例については、「XSD スキーマを使用して XML を検証する方法」を参照してください。
  • XML の表示。 ユーザー インターフェイスでは、XML データを自動的に表示できません。 データをデータセットに変換するために XSLT スタイル シートを記述できますが、スタイル シートの記述は簡単ではありません。 しかし、スタイル シートにより、XML を HTML のような表示可能な形式に変換できます。 詳細については、付録の「.NET アプリケーションでプログラムからスタイル シートを適用する方法」を参照してください。
  • XML の解析。 XML を解析するには、Microsoft .NET Framework クラス ライブラリに同梱された DOM (ドキュメント オブジェクト モデル) または XmlReader クラスを使用できます。 XmlReader は、 XML データの高速前方参照のみの読み取り専用アクセスを提供しますが、DOM はランダムな読み取りまたは書き込みアクセスを提供するので、より柔軟性があります。 ただし、DOM を使用した XML ドキュメントの解析には時間がかかるため、XmlDocument のインスタンス (または別の XML パーサー クラス) を作成して、XML ドキュメント全体をメモリに読み込む必要があります。
  • XML の並べ替え。 XML データを自動的に並べ替えることはできません。 代わりに、以下の方法のいずれかを使用します。
    • あらかじめ並べ替えた順番でデータを配信します。 このオプションは、呼び出し側アプリケーションでの動的なデータの並べ替えをサポートしません。
    • データを動的に並べ替えるために XSLT スタイル シートを適用します。 必要に応じて、DOM を使用して、実行時に XSLT スタイル シート内の並べ替えの条件を変更できます。
    • XML データをデータセットに変換し、DataView オブジェクトを使用してデータ要素の並べ替えと検索を行います。
  • プライベート フィールドの使用。 情報を非表示にするオプションはありません。

汎用データセットとしてのビジネス エンティティの表現

汎用データセットは、DataSet クラスのインスタンスです。このクラスは ADO.NET の System.Data 名前空間で定義されています。DataSet オブジェクトには、データ アクセス ロジック コンポーネントがデータベースから取得した情報を表現する DataTable オブジェクトが 1 つ以上含まれます。

図 7 に、Product ビジネス エンティティの汎用データセット オブジェクトを示します。 この DataSet オブジェクトには、製品情報を格納する DataTable が 1 つあります。 この DataTable には、ProductID 列が主キーであることを示す UniqueConstraint オブジェクトがあります。 データ アクセス ロジック コンポーネントで DataSet を作成するときに、DataTable オブジェクトと UniqueConstraint オブジェクトが作成されます。

Dd297667.f01boa07(ja-jp,MSDN.10).gif

図 7. Product ビジネス エンティティの汎用データセット

図 8 に、Order ビジネス エンティティの汎用データセット オブジェクトを示します。 この DataSet オブジェクトには DataTable オブジェクトが 2 つあり、それぞれに注文と注文明細に関する情報が格納されます。 DataTable には、それぞれのテーブルに対応した、主キーを特定する UniqueConstraint オブジェクトがあります。 また、DataSet には、注文と注文明細を関連付ける Relation オブジェクトがあります。

Dd297667.f01boa08(ja-jp,MSDN.10).gif

図 8. Order ビジネス エンティティの汎用データセット

次のコードでは、データ アクセス ロジック コンポーネントから汎用データセットを取得し、DataGrid コントロールに連結して、データ アクセス ロジック コンポーネントに渡し、データの変更を保存する方法が示されています。


// ProductDALC オブジェクトを作成します
ProductDALC dalcProduct = new ProductDALC();

// 全製品の情報を含んだデータセットを取得する ProductDALC のメソッドを呼び出します
DataSet dsProducts = dalcProduct.GetProducts();

// クライアントのデータセットを使用します。 たとえば、データセットとユーザー インターフェイス コントロールを連結します
dataGrid1.DataSource = dsProducts.Tables[0].DefaultView;
dataGrid1.DataBind();

// 準備ができたら、更新したデータセットを ProductDALC に渡して変更を保存します
// データベースに戻ります
dalcProduct.UpdateProducts(dsProducts);

データセットのテーブル、制約、およびリレーションは実行時にもクエリおよび変更が可能です。 詳細については、「DataSet の作成および使用」を参照してください。

ビジネス エンティティを汎用データセットとして表現した場合の長所を以下に示します。

  • 柔軟性。 データセットにはデータのコレクションを格納できます。また、データの複雑な関係を表現できます。
  • シリアル化。 データセットは、層間で受け渡されるときに、シリアル化をネイティブにサポートします。
  • データ連結。 Windows フォーム アプリケーションや ASP.NET アプリケーションのユーザー インターフェイス コントロールにデータセットを連結できます。
  • 並べ替えとフィルタ処理。 データセットは、DataView オブジェクトを使用して並べ替えやフィルタ処理を適用できます。 データをさまざまな方法で表示するために、同一のデータセットに対して複数の DataView オブジェクトを作成することができます。
  • XML との交換可能性。 データセットは XML 形式で読み書きできます。 これは、リモート アプリケーションや非接続のアプリケーションにとっては、データセットを XML 形式で取得し、DataSet オブジェクトをローカルに再作成できる便利な技法です。 また、アプリケーションがデータベースに接続していない間は、データセットを XML 形式で保持できます。
  • メタデータが利用できる。 データセットには、XSD スキーマという形態で完全なメタデータが用意されています。 DataSet クラス、DataTable クラス、DataColumn クラス、Constraint クラス、および Relation クラスのメソッドを使用して、プログラムによりデータセットのメタデータを取得することもできます。
  • オプティミスティック同時実行制御。 データを更新しているときに、データセットをデータ アダプタと併用してオプティミスティック同時実行制御のチェックを簡単に実行できます。
  • 拡張性。 データベース スキーマを変更すると、データ アクセス ロジック コンポーネントのメソッドによりデータセットが作成されることがあります。このデータセットには、必要に応じて変更済みの DataTable オブジェクトや DataRelation オブジェクトが含まれます。 データ アクセス ロジック コンポーネントのメソッド シグニチャは変更されません。 呼び出し側アプリケーションを変更して、データセットで新しい要素を使用するようにすることができます。

ビジネス エンティティを汎用データセットとして表現した場合の短所を以下に示します。

  • クライアントのコードでは、データセットのコレクションからデータにアクセスする必要があります。 クライアントのコードでデータセットのテーブルにアクセスするには、整数インデクサまたは文字列インデクサを使用して DataTable のコレクションにインデックスを付ける必要があります。 特定の列にアクセスするには、列番号または列名を使用して、DataColumn コレクションにインデックスを付ける必要があります。 以下の例では、Product テーブルの最初の行の ProductName 列にアクセスする方法を示しています。

    
    A‚A…
    // dsProducts と呼ばれる、データセットの先頭行の製品の製品名を取得します
    // コレクションは 0 から始まることに注意してください
    String str = (String)dsProducts.Tables["Products"].Rows[0]["ProductName"];
    A‚A…
    

       コンパイル時にはインデクサの値がチェックされません。 指定したテーブル名、列名、列型のいずれかが無効だった場合、実行時にエラーがトラップされます。 汎用データセットを使用するときは、IntelliSense を利用できません。

  • インスタンスの作成とマーシャリングの負荷が高くなります。 データセットからは、複数のサブオブジェクト (DataTable, DataRow, and DataColumn) が生成されます。したがって、XML 文字列やカスタム エンティティ コンポーネントよりもインスタンスの作成やマーシャリングに時間がかかる場合があります。 データの量が増加すると、データセットの内部構造を作成する際のオーバーヘッドがデータセットにデータを格納する場合よりも大幅に減少するので、データセットのパフォーマンスは相対的に向上します。

  • プライベート フィールド。 情報を非表示にするオプションはありません。

型指定されたデータセットとしてのビジネス エンティティの表現

型指定されたデータセットは、データセットのデータとメタデータを公開するために、厳密に型指定されたメソッド、プロパティ、および型定義を含むクラスです。 型指定されたデータセットの作成方法の詳細については、付録の「型指定されたデータセットを作成する方法」を参照してください。

以下の一覧に、汎用データセットと比較した場合の型指定されたデータセットの長所と短所を示します。 型指定されたデータセットのインスタンスの作成とマーシャリングの実行は、汎用データセットの場合とほぼ同じであることに注意してください。

ビジネス エンティティを型指定されたデータセットとして表現した場合の長所を以下に示します。

  • コードの読みやすさ。 型指定されたデータセットのテーブルや列にアクセスするには、次のコードで示すように、型指定されたメソッドやプロパティを使用できます。

    
    A‚A…
    // dsProducts と呼ばれる、型指定されたデータセットの先頭行の製品の製品名を取得します
    // コレクションは 0 から始まることに注意してください
    String str = dsProducts.Products[0].ProductName;
    A‚A…
    

    この例では、dsProducts は型指定されたデータセットのインスタンスです。 データセットには、Products という名前のプロパティで公開されている、DataTable が 1 つあります。 この DataTable の列は、ProductName などのプロパティで公開され、(単純にオブジェクトを返すのではなく) その列に適したデータ型を返します。

    型指定されたデータセットは、型指定されたメソッドとプロパティの提供によって、汎用データセットよりも簡単に使用できるようになります。 型指定されたデータセットを使用するときは、IntelliSense を利用できます。

  • コンパイル時の型のチェック。 実行時ではなくコンパイル時に、無効なテーブル名や列名が検出されます。

ビジネス エンティティを型指定されたデータセットとして表現した場合の短所を以下に示します。

  • 配置。 型指定されたデータセット クラスを含むアセンブリは、ビジネス エンティティを使用するすべての層に配置する必要があります。
  • Enterprise Services (COM+) の呼び出し側のサポート。 型指定されたデータセットが COM+ クライアントによって使用される場合、型指定されたデータセット クラスを含むアセンブリには厳密名を指定する必要があります。また、クライアント コンピュータに登録する必要もあります。 通常、アセンブリはグローバル アセンブリ キャッシュにインストールされます。 このような手順は、本書の後半で説明するように、カスタム エンティティ クラスにも必要となります。
  • 拡張性の問題点。 データベース スキーマを変更した場合、型指定されたデータセット クラスを再生成して、新しいスキーマをサポートする必要がある場合があります。 再生成のプロセスでは、型指定されたデータセット クラスに実装されたカスタム コードは保持されません。 型指定されたデータセット クラスを含むアセンブリは、すべてのクライアント アプリケーションに再配置する必要があります。
  • インスタンス作成。 new 演算子を使用して型のインスタンスを作成することはできません。
  • 継承。 型指定されたデータセットは、データセットから継承する必要があります。そのため、他の基本クラスを使用できません。

カスタム ビジネス エンティティ コンポーネントの定義

ビジネス エンティティを表すカスタム クラスには、通常、以下のメンバが含まれています。

  • ビジネス エンティティのデータをローカルにキャッシュするためのプライベート フィールド。 このフィールドには、データ アクセス ロジック コンポーネントによってデータベースからデータが取得されたときに、データベース内のデータのスナップショットが保持されます。
  • エンティティの状態、およびエンティティ内部のデータのサブコレクションと階層にアクセスするためのパブリック プロパティ。 プロパティには、データベースの列名と同じ名前を付けることができますが、絶対条件ではありません。 データベース内の名前ではなく、アプリケーションの必要に応じたプロパティ名を選択します。
  • エンティティ コンポーネントのデータを使用してローカライズされた処理を実行するためのメソッドとプロパティ。
  • エンティティ コンポーネントの内部状態の変更を知らせるためのイベント。

図 9 では、カスタム エンティティ クラスの使用方法が示されています。 エンティティ クラスには、データ アクセス ロジック コンポーネントまたは元になるデータベースに関する情報が含まれていないことに注意してください。すべてのデータベース アクセスは、データ アクセス ロジック コンポーネントによって実行され、データ アクセス ポリシーとビジネス ロジックを集中管理します。 また、層間でのビジネス エンティティ データの受け渡し方法は、ビジネス エンティティの表現形式には直接関係していません。たとえば、ビジネス エンティティをオブジェクトとしてローカルに表すことができ、ビジネス エンティティ データを別の層に渡すには、別の方法 (スカラ値や XML など) を選択できます。

クリックすると拡大表示されます

図 9. カスタム ビジネス コンポーネントのロール (画像をクリックすると拡大表示されます)

カスタム ビジネス エンティティ コンポーネントを定義する場合の推奨事項

カスタム エンティティ コンポーネントの実装を支援するには、以下の推奨事項を考慮します。

  • 構造体にするか、クラスにするかを選択します。 階層的なデータまたはコレクションを保持しない単純なビジネス エンティティの場合は、そのビジネス エンティティを表す構造体を定義することを検討します。 複雑なビジネス エンティティの場合、または継承を必要とするビジネス エンティティの場合は、そのエンティティをクラスとして定義します。 構造体型とクラス型の比較については、「構造体とクラス」を参照してください。

  • ビジネス エンティティの状態を表現します。 数値や文字列などの単純な値の場合は、対応する等価な .NET データ型を使用してフィールドを定義します。 カスタム エンティティを定義する方法を示すコード例については、付録の「ビジネス エンティティ コンポーネントを定義する方法」を参照してください。

  • カスタム ビジネス エンティティ コンポーネント内でサブコレクションや階層を表現します。 カスタム エンティティ内でデータのサブコレクションや階層を表現するには、次の 2 つの方法があります。

    • ArrayList などの .NET コレクション。 .NET コレクション クラスにより、サイズ変更可能なコレクションにとって役立つプログラミング モデルが提供され、ユーザー インターフェイス コントロールとのデータ連結の組み込みサポートも用意されます。

    • データセット。 データセットは、リレーショナル データベースまたは XML ドキュメントからのデータのコレクションや階層を格納するのに非常に適しています。 さらに、サブコレクションのフィルタ処理、並べ替え、またはデータ連結が必要な場合に、データセットが選択されます。

      カスタム エンティティ内でデータのコレクションや階層を表現する方法を示すコード例については、付録の「ビジネス エンティティ コンポーネント内のデータのコレクションと階層を表現する方法」を参照してください。

  • ユーザー インターフェイス クライアントのデータ連結をサポートします。 カスタム エンティティがユーザー インターフェイスで使用され、自動的なデータ連結を利用する場合は、カスタム エンティティにデータ連結を実装する必要があることがあります。 次のようなシナリオを検討します。

    • Windows フォームのデータ連結。 カスタム エンティティにデータ連結インターフェイスを実装しないで、エンティティ インスタンスをコントロールにデータ連結できます。 また、エンティティの配列またはエンティティの .NET コレクションもデータ連結できます。

    • Web フォームのデータ連結。 IBindingList インターフェイスを実装しないで、エンティティ インスタンスを Web フォームのコントロールにデータ連結することはできません。 ただし、セットだけにデータ連結する場合は、カスタム エンティティに IBindingList インターフェイスを実装する必要なしに、配列または .NET コレクションを使用できます。

      カスタム エンティティをユーザー インターフェイス コントロールに連結する方法を示すコード例については、付録の「ビジネス エンティティ コンポーネントをユーザー インターフェイス コントロールに連結する方法」を参照してください。

  • 内部データの変更に関するイベントを公開します。 データを表示する場所にかかわらず、データを最新状態に更新できるので、リッチ クライアント ユーザー インターフェイスの設計では、イベントを公開することが役に立ちます。 イベントは、サーバーのデータ変更に関するものではなく、内部状態の変更に関するものだけを公開します。 カスタム エンティティでイベントを公開する方法を示すコード例については、付録の「ビジネス エンティティ コンポーネントでイベントを公開する方法」を参照してください。

  • ビジネス エンティティをシリアル化可能にします。 ビジネス エンティティをシリアル化可能にすることによって、データベースとの対話なしに、ビジネス エンティティの状態を中間状態で保存できます。 その結果、オフラインでのアプリケーション開発や、完成するまでビジネス データに影響しない複雑なユーザー インターフェイス プロセスの設計が容易になります。 シリアル化には次の 2 種類があります。

    • XmlSerializer クラスを使用する XML シリアル化。 パブリック フィールドや読み取りと書き込みが可能なパブリック プロパティを XML にシリアル化する必要があるときに、XML シリアル化を使用します。 Web サービスからビジネス エンティティ データを返す場合は、XML シリアル化により、オブジェクトが自動的に XML にシリアル化されることに注意してください。

      エンティティ内部に新たなコードを実装しなくても、ビジネス エンティティで XML シリアル化を実行できます。 ただし、オブジェクトのパブリック フィールドと読み取りと書き込みが可能なパブリック プロパティだけが XML にシリアル化されます。 プライベート フィールド、インデクサ、プライベート プロパティ、読み取り専用プロパティ、およびオブジェクト グラフはシリアル化されません。 結果として作成される XML は、カスタム エンティティの属性を使用して管理できます。 カスタム エンティティ コンポーネントを XML 形式にシリアル化する方法の詳細については、付録の「ビジネス エンティティ コンポーネントを XML 形式にシリアル化する方法」を参照してください。

    • BinaryFormatter クラスまたは SoapFormatter クラスを使用した書式設定されたシリアル化。 すべてのパブリック フィールドとプライベート フィールド、およびオブジェクトのオブジェクト グラフをシリアル化する必要があるとき、またはエンティティ コンポーネントをリモート処理サーバーとの間で受け渡しする場合、書式設定されたシリアル化を使用します。

      フォーマッタ クラスにより、パブリックでもプライベートでも、オブジェクトのすべてのフィールドとプロパティがシリアル化されます。 BinaryFormatter により、オブジェクトがバイナリ形式でシリアル化され、SoapFormatter によりオブジェクトが SOAP 形式でシリアル化されます。 BinaryFormatter を使用するシリアル化は、SoapFormatter を使用するシリアル化よりも高速になります。 いずれかのフォーマッタ クラスを使用する場合は、エンティティ クラスを [Serializable] 属性でマークする必要があります。 シリアル化形式を明示的に制御する必要がある場合は、クラスで ISerializable インターフェイスも実装する必要があります。 書式設定されたシリアル化を使用する方法の詳細については、付録の「ビジネス エンティティ コンポーネントをバイナリ形式にシリアル化する方法」と「ビジネス エンティティ コンポーネントを SOAP 形式にシリアル化する方法」を参照してください。

       オブジェクトのシリアル化が解除されるときは、既定のコンストラクタは呼び出されません。 シリアル化解除のパフォーマンス上の理由からこの制約が課せられています。

カスタム エンティティを定義する場合の長所を以下に示します。

  • コードの読みやすさ。 カスタム エンティティ クラスのデータにアクセスするために、次のコードで示されているような、型指定されたメソッドやプロパティを使用できます。

    
    // ProductDALC オブジェクトを作成します
    ProductDALC dalcProduct = new ProductDALC();
    
    // ProductDALC オブジェクトを使用して、ProductEntity オブジェクトを作成します。
    // このコードは、ProductDALC クラスが GetProduct というメソッドを所有し、
    // パラメータに製品 ID (この例では 21) を使用して、この製品のすべての
    // データを含んでいる ProductEntity オブジェクトを返すことを想定しています。
    ProductEntity aProduct = dalcProduct.GetProduct(21);
    
    // この製品の製品名を変更します
    aProduct.ProductName = "Roasted Coffee Beans";
    

    上記の例では、製品は ProductEntity という名前のカスタム エンティティ クラスのインスタンスです。 ProductDALC クラスに GetProduct という名前のメソッドがあります。このメソッドは、ProductEntity オブジェクトを作成し、特定の製品のデータを持つオブジェクトを生成し、ProductEntity オブジェクトを返します。 呼び出し側アプリケーションは、ProductName などのプロパティを使用して ProductEntity オブジェクトのデータにアクセスできます。また、メソッドを呼び出してオブジェクトを操作できます。

  • カプセル化。 カスタム エンティティには、1 つのビジネス ルールをカプセル化するためのメソッドを含めることができます。 これらのメソッドは、データベースに存在するデータにアクセスするのではなく、エンティティ コンポーネントにキャッシュされたビジネス エンティティのデータを操作します。 次の例を考えてみます。

    
    // ProductEntity クラスで定義されているメソッドを呼び出します。
    aProduct.IncreaseUnitPriceBy(1.50);
    

    上記の例では、呼び出し側アプリケーションにより、ProductEntity オブジェクトの IncreaseUnitPriceBy という名前のメソッドが呼び出されます。 この変更は、呼び出し側アプリケーションによって ProductDALC オブジェクトの適切なメソッドが呼び出されたときに、ProductEntity オブジェクトがデータベースに返されて保存されるまでは適用されません。

  • 複雑なシステムのモデル化。 異なるビジネス エンティティ間で多くの相互作用がある複雑なドメイン問題をモデル化する場合、適切に定義されたクラス インターフェイスの複雑性を緩和するために、カスタム エンティティ クラスを定義することが有用な場合があります。

  • ローカライズされた検証。 カスタム エンティティ クラスでは、有効なビジネス エンティティ データを検出するために、プロパティ アクセサで簡単な検証テストを実行できます。 詳細については、「ビジネス エンティティ コンポーネントのプロパティ アクセサでデータを検証する方法」を参照してください。

  • プライベート フィールド。 呼び出し側に公開しない情報を非表示にできます。

カスタム エンティティを定義する場合の短所を以下に示します。

  • ビジネス エンティティのコレクション。 カスタム エンティティは、ビジネス エンティティのコレクションではなく、1 つのビジネス エンティティを表現します。 呼び出し側アプリケーションは、配列または .NET コレクションを作成し、複数のビジネス エンティティを保持する必要があります。
  • シリアル化。 カスタム エンティティに独自のシリアル化メカニズムを実装する必要があります。 属性を使用してエンティティ コンポーネントをシリアル化する方法を制御できます。または、ISerializable インターフェイスを実装して独自のシリアル化を制御できます。
  • ビジネス エンティティの複雑なリレーションシップと階層の表現。 ビジネス エンティティ コンポーネント内のデータのリレーションシップと階層を表現するために、独自のメカニズムを実装する必要があります。 上記で説明したように、これを実現する方法としては、DataSet を使用するのが最も簡単な方法です。
  • データの検索と並べ替え。 エンティティの検索と並べ替えをサポートするために、独自のメカニズムを定義する必要があります。 たとえば、IComparable インターフェイスを実装して、SortedList コレクションまたは Hashtable コレクションにエンティティ コンポーネントを保持できるようにします。
  • 配置。 すべての物理層に、カスタム エンティティを保持するアセンブリを配置する必要があります。
  • Enterprise Services (COM+) クライアントのサポート。 カスタム エンティティが COM+ クライアントによって使用される場合、エンティティを保持するアセンブリには厳密名を指定する必要があります。また、クライアント コンピュータに登録する必要もあります。 通常、アセンブリはグローバル アセンブリ キャッシュにインストールされます。
  • 拡張性の問題点。 データベース スキーマが変更されると、カスタム エンティティ クラスを変更してアセンブリを再配置する必要があります。

CRUD 操作を備えたカスタム ビジネス エンティティ コンポーネントの定義

カスタム エンティティを定義するときに、元になるデータ アクセス ロジック コンポーネントの CRUD 操作を完全にカプセル化する方法を指定できます。 これは、より伝統的なオブジェクト指向のアプローチで、複雑なオブジェクト ドメインに適しています。 クライアント アプリケーションは、データ アクセス ロジック コンポーネント クラスに直接アクセスできなくなりました。 代わりに、クライアント アプリケーションはエンティティ コンポーネントを作成し、エンティティ コンポーネントで CRUD メソッドを呼び出します。 呼び出されたメソッドは、元になるデータ アクセス ロジック コンポーネントに転送されます。

図 10 は、CRUD 操作を備えたカスタム エンティティ クラスの役割を示しています。

クリックすると拡大表示されます

図 10. CRUD 操作を備えたカスタム ビジネス コンポーネントの役割 (画像をクリックすると拡大表示されます)

CRUD 操作を備えたカスタム エンティティ クラスを定義する場合の長所を以下に示します。

  • カプセル化。 カスタム エンティティは、元になるデータ アクセス ロジック コンポーネントで定義されている操作をカプセル化します。
  • 呼び出し側へのインターフェイス。 呼び出し側は、ビジネス エンティティ データを保存するために、1 つのインターフェイスだけを処理する必要があります。 データ アクセス ロジック コンポーネントに直接アクセスする必要はありません。
  • プライベート フィールド。 呼び出し側に公開しない情報を非表示にできます。

CRUD 操作を備えたカスタム エンティティ クラスを定義する場合の短所を以下に示します。

  • ビジネス エンティティ セットの処理。 カスタム エンティティのメソッドは、1 つのビジネス エンティティ インスタンスに関連します。 ビジネス エンティティのセットをサポートするために、配列またはエンティティ コンポーネントのコレクションを受け取るか返す、静的なメソッドを定義できます。
  • 開発時間の増加。 従来のオブジェクト指向のアプローチでは、通常、データセットなどの既存のオブジェクトでの作業よりも、設計や開発により多くの作業が必要になります。

データの表現と層間でのデータの受け渡しに関する推奨事項

アプリケーション全体のデータを表現する方法、および層間でのデータの受け渡し方法は、必ずしも同じである必要はありません。 ただし、一貫性のある限定された形式のセットを持つことにより、変換層を追加する必要性が減少するというパフォーマンスと保守の利点が得られます。

使用するデータ形式は、特定のアプリケーションの必要条件や、データでの作業方法によって異なる必要があります。 データを表現するための共通の方法はありません。この主な理由は、現在のアプリケーションの多くが複数の呼び出し側をサポートする必要があるためです。 ただし、次のような一般的なガイドラインに従うことをお勧めします。

  • アプリケーションが、主に、並べ替え、検索、およびデータ連結などの機能が設定され、必要とされている状態で動作する場合、データセットをお勧めします。 ただし、アプリケーションがインスタンス データで動作する場合、スカラ値を使用する方が適しています。
  • アプリケーションがインスタンス データで主に動作する場合、カスタム ビジネス エンティティ コンポーネントが最善の選択となる場合があります。このコンポーネントを使用すると、データセットで 1 行が表現されるときのオーバーヘッドの発生を防げるためです。
  • 多くの場合、データ中心の形式 (XML ドキュメントやデータセットなど) を使用するようにアプリケーションを設計します。 データセットによって提供される柔軟性とネイティブ機能を使用すると、複数クライアントのより容易なサポート、カスタム コード量の削減、および多くの開発者に親しみのあるプログラミング API の使用が可能になります。 オブジェクト指向形式のデータを使用した作業で、いくつかの利点が提供される場合でも、カスタム コーディングを必要とする複雑なビジネス エンティティにより、提供する機能の数に応じて開発コストや保守コストが増加します。

トランザクション

最近の大部分のアプリケーションでは、システムのデータの整合性を維持するために、トランザクションをサポートする必要があります。 トランザクション管理にはいくつかのアプローチがありますが、いずれのアプローチも次の 2 つのプログラミング モデルのいずれかに該当します。:

  • 手動トランザクション。 コンポーネント コードまたはストアド プロシージャで ADO.NET または Transact-SQL のいずれかのトランザクション サポート機能を個別に使用するコードを記述します。
  • 自動トランザクション。 Enterprise Services (COM+) を使用して、実行時にオブジェクトのトランザクション要件を指定する宣言的な属性を .NET クラスに追加します。 このモデルを使用して、同一のトランザクション内で作業を実行する複数のコンポーネントを容易に構成できます。

ここでは、データ アクセス ロジック コンポーネントとビジネス エンティティ コンポーネントにトランザクション サポートを実装するのに役立つガイダンスと推奨事項を示します。 .NET でのトランザクションの例と詳細情報については、「.NET データ アクセス アーキテクチャ ガイド」を参照してください。

トランザクションの実装

大部分の環境では、トランザクションのルートは、データ アクセス ロジック コンポーネントやビジネス エンティティ コンポーネントではなく、ビジネス プロセスになります。 それは、一般的にトランザクションを必要とするビジネス プロセスは単一のビジネス エンティティだけではなく、複数のビジネス エンティティに展開されるためです。

ただし、高度なビジネス プロセスの支援なしに、単一のビジネス エンティティでトランザクション操作の実行が必要な状況が生じることもあります。 たとえば、前半で説明したデータベースに新しい顧客を追加するには、以下の複数の操作を実行する必要があります。

  • Customer テーブルに新しい行を挿入します。
  • Address テーブルに新しい行を 1 行以上挿入します。

どちらの操作も成功する必要があります。いずれかが失敗した場合は、その顧客はデータベースに追加されません。 Customer ビジネス エンティティがトランザクションを起動する大規模なビジネス プロセスの一部ではない場合は、Customer ビジネス エンティティ内部で手動トランザクションを使用します。 手動トランザクションは、Microsoft 分散トランザクション コーディネータ (DTC) とのプロセス間通信をまったく必要としないので、自動トランザクションよりもはるかに高速になります。

図 11 では、手動トランザクションと自動トランザクションのどちらを使用するかを決定する方法が示されています。 推奨アプローチは、COM+ トランザクションのオーバーヘッドにより、可能な個所では、データベースにトランザクションをプッシュし、ストアド プロシージャ内でトランザクション動作を管理することです。

Dd297667.f01boa11(ja-jp,MSDN.10).gif

図 11. トランザクションの実装方法の決定

   ASP.NET ベースのクライアントから呼び出していて、トランザクションを開始するビジネス プロセスが存在しない場合、ASP.NET コード ビハインドでトランザクションを開始したいと考えるかもしれません。 このような設計はお勧めしません。ASP.NET ベースのクライアントからは決してトランザクションを開始しないでください。 代わりに、データのプレゼンテーションをビジネス プロセスから分離します。 プレゼンテーション層は他の層に物理的に展開される最も一般的な層なので、ネットワークの待機時間などの問題により、パフォーマンスも問題になります。

データ アクセス ロジック コンポーネントで手動トランザクションを使用する場合の推奨事項

データ アクセス ロジック コンポーネントに手動トランザクションを実装するときは、以下の推奨事項を考慮してください。

  • 可能な場合は、ストアド プロシージャ内で処理を実行します。 Transact-SQL ステートメントの BEGIN TRANSACTION、END TRANSACTION、および ROLLBACK TRANSACTION を使用して、トランザクションを管理します。 コード例については、「.NET データ アクセス アーキテクチャ ガイド」の「Transact-SQL を使用してトランザクションを実行する方法」を参照してください。
  • ストアド プロシージャを使用しておらず、データ アクセス ロジック コンポーネントがビジネス プロセスから呼び出されない場合は、プログラムで ADO.NET を使用してトランザクションを管理できます。 コード例については、「.NET データ アクセス アーキテクチャ ガイド」の「ADO.NET の手動トランザクションをコーディングする方法」を参照してください。

データ アクセス ロジック コンポーネントで自動トランザクションを使用する場合の推奨事項

自動トランザクションでは、COM+ トランザクションに関連するオーバーヘッドがあったとしても、手動トランザクションよりも簡単なプログラミング モデルが提供されます。さらに、トランザクションが複数のデータ ソースに展開され、DTC と関連して機能する場合には自動トランザクションが必要になります。 データ アクセス ロジック コンポーネントに自動トランザクションを実装する場合は、以下の推奨事項を考慮してください。

  • データ アクセス ロジック コンポーネントを System.EnterpriseServices 名前空間の ServicedComponent クラスから継承する必要があります。 COM+ サービスに登録されるすべてのアセンブリは、厳密名を所持する必要があることに注意してください。 厳密名付きアセンブリの詳細については、「厳密な名前付きアセンブリの作成と使用」を参照してください。
  • データ アクセス ロジック コンポーネントに Transaction(TransactionOption.Supported) 属性を付けます。その結果、同一のコンポーネント内で、読み取り操作と書込み操作を実行できます。 Transaction(TransactionOption.Required) が常にトランザクションを必要とするのとは異なり、このオプションにより、トランザクションが不要な場合はトランザクションのオーバーヘッドが回避されます。

以下のコード サンプルでは、データ アクセス ロジック コンポーネントで自動トランザクションをサポートする方法が示されています。


using System.EnterpriseServices;

[Transaction(TransactionOption.Supported)]
public class CustomerDALC : ServicedComponent
{
  ...
}

自動トランザクションを使用する場合は、データ アクセス ロジック コンポーネントにより、操作が成功したか失敗したかがトランザクション内で決定される必要があります。 これを暗黙に決定するには、メソッドに AutoComplete 属性を付け、操作が失敗した場合は例外をスローします。 明示的に決定するには、ContextUtil クラスで SetComplete メソッドまたは SetAbort メソッドを呼び出します。

自動トランザクションの詳細については、「.NET データ アクセス アーキテクチャ ガイド」の「自動トランザクションを使用する」を参照してください。

ビジネス エンティティ コンポーネントの自動トランザクションの使用

複数の操作を備えたカスタム ビジネス エンティティを実装する場合は、自動トランザクションを使用して、このようなオブジェクトのトランザクション操作を指定できます。 ビジネス エンティティ コンポーネントのトランザクション動作を指定するために自動トランザクションを使用する場合の推奨事項は、データ アクセス ロジック コンポーネントに自動トランザクションを実装する場合に上記に一覧した推奨事項と同じです。

**注   **ビジネス エンティティ コンポーネントがトランザクション内で成功と失敗を決定する必要のあるビジネス ロジックを保持していない場合は、そのトランザクション コンテキストをすべて無視できます。 カスタム ビジネス エンティティ コンポーネントを ServicedComponent から継承する必要はありません。トランザクション コンテキストは処理されますが、エンティティ コンポーネントはそのコンテキストを無視します。

検証

アプリケーション内のさまざまな層でデータ検証を実行できます。 各層にとって適切な検証の種類は、以下のように異なります。

  • クライアント アプリケーションは、ビジネス エンティティ データを送信する前に、そのデータをローカルに検証できます。
  • ビジネス プロセスは、ビジネス ドキュメントを受け取ったときに、XSD スキーマを使用することによって、そのドキュメントを検証できます。
  • データ アクセス コンポーネントとストアド プロシージャは、参照整合性を確認するために、および制約や重要なビジネス ルールを設定するために、データを検証できます。

検証には、次の 2 つの一般的な種類があります。

  • 特定時点での検証。 これは、特定の時点に行われる検証です。 たとえば、ビジネス プロセスが XML ドキュメントを受け取った時点にそのドキュメントを検証する場合などです。
  • 継続的な検証。 これは、アプリケーションの多くの異なるレベルで継続的に行われる検証です。 継続的な検証の例には、以下の検証があります。
    • ユーザーが長すぎる文字列を入力しないように、ユーザー インターフェイスは、フィールドの最大長を指定できます。
    • データセットは、データ列の最大長を指定できます。
    • カスタム ビジネス エンティティ コンポーネントは、エンティティ データで範囲チェック、長さチェック、Null 以外値のチェック、およびその他の簡単なチェックを実行できます。
    • データ アクセス ロジック コンポーネント、ストアド プロシージャ、およびデータベース自体は、データをデータベースに保存する前に、データが有効であることを確認するために、同じようなテストを実行できます。

時には、帯域外集計や変換プロセスを実装する場合もあります。 このようなアプローチは、検証や変換が頻繁に変更される場合は便利ですが、パフォーマンスに悪影響を与えます。 たとえば、ある ISV が同じコンポーネントを使用して、2 つのバージョンのデータベース スキーマをサポートする場合、2 つのバージョンのデータベース スキーマ間で検証と変換を行うために、個別のコンポーネントを作成できます。

XSD スキーマを使用して XML を検証する方法

XSD スキーマに対して XML ドキュメントを検証するには、以下の手順を実行します。

  1. 以下のコードで示すように、XmlTextReader オブジェクトをラップする XmlValidatingReader オブジェクトを作成します。

    
    ' XmlValidatingReader オブジェクトを作成し、Product.xml の読み取りと検証を行います
    XmlTextReader tr = new XmlTextReader("Product.xml");
    XmlValidatingReader vr = new XmlValidatingReader(tr);
    
  2. ValidationType 列挙値を使用することによって、必要な検証の種類を指定します。 .NET Framework では、次の 3 種類の検証がサポートされます。

    • DTD (Document Type Definition)。ValidationType.DTD を指定します。

    • Microsoft XDR (XML Data-Reduced) スキーマ。ValidationType.XDR を指定します。

    • W3C 標準 XSD スキーマ。ValidationType.Schema を指定します。

      以下のコードでは、ValidationType 列挙値の使い方が示されています。

      
      vr.ValidationType = ValidationType.Schema;  ' XSD スキーマ検証を指定します
      
  3. 以下のコードに示すように、検証イベント ハンドラ メソッドを登録します。

    
    vr.ValidationEventHandler += new ValidationEventHandler(MyHandlerMethod);
    
  4. 以下のコードに示すように、検証イベント ハンドラ メソッドの実装を用意します。

    
    public void MyHandlerMethod(object sender, ValidationEventArgs e)
    {
       Console.WriteLine("Validation Error: " + e.Message);
    }
    
  5. 以下のコードで示すように、ドキュメントを読み取って、検証します。 検証エラーは、検証イベント ハンドラ メソッドによって取得されます。

    
    try
    {
       while (vr.Read())
       {
          // 必要に応じて XML データを処理します ...
       }
    }
    catch (XmlException ex)
    {
       Console.WriteLine("XmlException: " + ex.Message);
    }
    vr.Close();
    

ビジネス エンティティ コンポーネントのプロパティ アクセサでデータを検証する方法

以下は、カスタム エンティティのプロパティ アクセサで、簡単な検証を行う方法を示すコードの一部です。 検証テストに失敗した場合は、問題点の性質を示すために、例外をスローできます。 特定のデータや形式を検証するために、set プロパティ アクセサ内部で正規表現を使用することもできます。


public class ProductDALC
{
 A‚A…
  public short ReorderLevel
  {
    get { return reorderLevel; }
  }
  set
  {
    if (value < 0)
    {
      throw new ArgumentOutOfRangeException("ReorderLevel cannot be negative.");
    }
    reorderLevel = value;
  }

  // ProductDALC クラスのその他のメンバを加えます...
}

例外管理

.NET アプリケーションでエラーが発生したときは、一般的には、メソッドからエラー値を返すのではなく、例外をスローすることをお勧めします。 この助言は、データ アクセス ロジック コンポーネントとビジネス エンティティ コンポーネントの作成方法に密接に関係します。 アプリケーションでは、次のような 2 種類の一般的な例外が発生します。

  • 以下のような技術的な例外。
    • ADO.NET
    • データベースへの接続
    • (データベース、ネットワーク共有、メッセージ キューなどの) リソースが使用できない
  • 以下のようなビジネス ロジックの例外。
    • 検証エラー
    • ビジネス ロジックを実装するストアド プロシージャでのエラー

データ アクセス ロジック コンポーネントで例外を管理する場合の推奨事項

データ アクセス ロジック コンポーネントは例外を伝達する必要があり、クライアントが例外を管理しやすくする場合のみ例外の種類をまとめます。 呼び出し側が多岐にわたる場合、例外処理構造や例外発行ロジックにとっては、例外を 2 つの主要な例外の種類 (技術的な例外とビジネス例外) にまとめることが役に立ちます。

アプリケーションは例外情報を発行する必要があります。 管理者または Microsoft Operations Manager のような Windows Management Instrumentation (WMI) 監視ツールによって監視されるログに、技術的な例外を発行できます。 ビジネス例外は、アプリケーション固有のログに発行できます。 一般的には、例外の全体的な状況を理解できるように、データ アクセス ロジックから例外を呼び出し側に伝達し、呼び出し側でその例外を発行できるようにします。

以下の例では、このような推奨事項が示されています。


public class CustomerDALC 
{
  public void UpdateCustomer(Dataset aCustomer)
  {
    try
    {
      // データベースの顧客を更新します...
    }
    catch (SqlException se)
    {
      // 例外をキャッチ、ラップし、再スローします
      throw new DataAccessException("Database is unavailable", se);
    }
    finally
    {
      // クリーンアップ コード
    }
  }
}

ビジネス エンティティ コンポーネントでの例外管理の推奨事項

ビジネス エンティティ コンポーネントは、呼び出し側に例外を伝達する必要があります。 ビジネス エンティティ コンポーネントが検証を実行する場合、または呼び出し側が操作に必要なデータをすべて提供しないで操作を試みる場合は、ビジネス エンティティ コンポーネントが例外を発生させることもできます。

以下の例では、ビジネス エンティティ コンポーネントが例外を発生する方法が示されています。 この例では、顧客が姓を指定しない場合に Update メソッドが例外をスローします。


public class CustomerEntity
{
  public void Update()
  {
   // ユーザーが必要なデータを指定したことをチェックします
   // この場合は顧客の姓が必要です
    if (FirstName == "" )
    {
       // ユーザーが定義した新しいアプリケーション例外をスローします
       throw new MyArgumentException("You must provide a First Name.);
    }
    ...
  }
}

.NET アプリケーションでの例外の処理に関する詳細については、「.NET における例外管理」を参照してください。 カスタム技術例外とカスタム ビジネス例外を「Exception Management Application Block」 (英語) で提供される ApplicationException クラスまたは BaseApplicationException クラスから継承できます。

承認とセキュリティ

ここでは、データ アクセス ロジック コンポーネントとビジネス エンティティ コンポーネントにセキュリティを提供する方法について説明します。 .NET 共通言語ランタイムは、アクセス許可オブジェクトを使用して、マネージ コードに制限事項を設定するメカニズムを実装します。 アクセス許可オブジェクトには、それぞれ固有の目的を持つ次の 3 種類のオブジェクトがあります。

  • コード アクセス セキュリティ。 リソースや操作が承認されずに使用されることを保護するために、この種のアクセス許可オブジェクトが使用されます。
  • ID。 この種のアクセス許可オブジェクトにより、アセンブリを実行するために必要な ID 特性が指定されます。
  • ロールベースのセキュリティ。 この種のアクセス許可オブジェクトにより、ユーザー (またはユーザーの代理として動作するエージェント) が特定の ID を所持するかどうか、または指定したロールのメンバであるかどうかを検出するためのメカニズムが提供されます。 PrincipalPermission オブジェクトが、唯一のロールベースのセキュリティ アクセス許可オブジェクトになります。

マネージ コードはプリンシパル オブジェクトを使用することによって、プリンシパルの ID またはロールを発見できます。プリンシパル オブジェクトには、ID オブジェクトへの参照が保持されます。 ID オブジェクトとプリンシパル オブジェクトを、ユーザー アカウントやグループ アカウントのような馴染みのある概念と比較すると理解しやすくなります。 .NET Framework では、ID オブジェクトがユーザーを表し、ロールがメンバシップやセキュリティ コンテキストを表します。 プリンシパル オブジェクトは、ID オブジェクトとロールの両方をカプセル化したものです。 .NET Framework のアプリケーションは、ID に基づいて、またはより一般的にはロールメンバシップに基づいて、プリンシパル オブジェクトに権限を許可します。

.NET でのアクセス許可とセキュリティの詳細については、「セキュリティの基本概念」を参照してください。

データ アクセス ロジック コンポーネントでのセキュリティの推奨事項

データ アクセス ロジック コンポーネントは、他のアプリケーション コンポーネントが使用することを目的に設計されるので、アプリケーション内のデータ アクセス ロジック コンポーネントが、呼び出し側がデータにアクセスする前にセキュリティを実装できる最終的な場所になります。

多くの場合、データ アクセス ロジック コンポーネントは、呼び出し側が設定するセキュリティ コンテキストに依存できます。 ただし、要求された動作をプリンシパルが実行することを許可されているかどうかを判断するために、データ アクセス ロジック コンポーネントが独自の承認チェックを行う必要がある状況もあります。 承認は認証後に行われ、プリンシパルの ID とロールに関する情報を使用して、そのプリンシパルがどのリソースにアクセスできるかを判断します。

以下のことが必要になる場合に、データ アクセス ロジック コンポーネント レベルで承認チェックを行います。

  • 完全に信頼していないビジネス プロセスの開発者とデータ アクセス ロジック コンポーネントを共有する必要がある場合。
  • データ ストアによって公開される強力な関数へのアクセスを保護する必要がある場合。

ID オブジェクトとプリンシパル オブジェクトを定義した後に、次の 3 つの方法で、ロールベースのセキュリティ チェックを実行できます。

  • PrincipalPermission オブジェクトを使用して、不可欠なセキュリティ チェックを行います。
  • PrincipalPermissionAttribute 属性を使用して、宣言によるセキュリティ チェックを行います。
  • Principal オブジェクトのプロパティと IsInRole メソッドを使用して、明示的なセキュリティ チェックを行います。

以下のコード サンプルにより、データ アクセス ロジック コンポーネント クラスのメソッドで宣言によりロールベースのセキュリティ チェックを指定するために、PrincipalPermissionAttribute 属性を使用する方法が示されています。


using System;
using System.Security.Permissions;

public class CustomerDALC 
{

  public CustomerDALC()
  {
  }

  // PrincipalPermissionAttribute を使用して、このメソッドの呼び出し側が
  // "MyUser" という ID を持ち、"Administrator" ロールに所属することを要求します
  [PrincipalPermissionAttribute(SecurityAction.Demand,
                                Name="MyUser", Role="Administrator")]
  public void DeleteCustomer(string customerID)
  {
    // ここで顧客コードを削除します
  }
}

以下の コードでは、CustomerDALC オブジェクトの DeleteCustomer メソッドを呼び出せるように、必要な ID とロールを持つプリンシパル オブジェクトを作成する方法が示されています。


using System;
using System.Security.Principal;
using System.Threading;

public class MainClass
{
  public static int Main(string[] args)
  {
    Console.Write("User Name: ");
    string UserName = Console.ReadLine();

    Console.Write("Password: ");
    string Password = Console.ReadLine();

    if (Password == "password" && UserName == "MyUser")
    {
      // "MyUser" という名前で汎用 ID を作成します
      GenericIdentity MyIdentity = new GenericIdentity("MyUser");

      // ロールを作成します
      String[] MyString = {"Administrator", "User"};

      // 汎用プリンシパルを作成します
      GenericPrincipal MyPrincipal = new GenericPrincipal(MyIdentity,
MyString);
      
      // ロールベースのセキュリティで使用するために、このスレッドの現在のプリンシパルを設定します
      Thread.CurrentPrincipal = MyPrincipal;
    }

    // CustomerDALC オブジェクトを作成し、DeleteCustomer メソッドの呼び出しを試みます
    // これは、現在のプリンシパルの ID とロールが OK の場合のみ成功します。
    CustomerDALC c = new CustomerDALC();
    c.DeleteCustomer("VINET");
  }
}

Windows 認証

データベースに接続するときは、理想的には SQL Server 認証ではなく、Windows 認証を使用することをお勧めします。 ただし、データベースでの偽装は接続プーリングを妨げるので、サービス アカウントを使用し、データベースでの偽装を避ける必要があります。 接続プーリングでは、同じ接続文字列が必要になります。データベースを開くのに異なる接続文字列を使用すると、別の接続プールが作成され、スケーラビリティを制限することになります。

Windows 認証と接続プーリングの詳細については、「.NET データ アクセス アーキテクチャ ガイド」の「データベース接続の管理」セクションを参照してください。

セキュリティで保護された通信の推奨事項

呼び出し側のアプリケーションとデータ アクセス ロジック コンポーネントの間でセキュリティで保護された通信を実現するには、以下の推奨事項を検討します。

  • さまざまな層から回線経由でデータ アクセス ロジック コンポーネントが呼び出され、その交換に保護する必要のある機密情報が関与している場合は、セキュリティで保護される通信テクノロジとして、DCOM (分散コンポーネント オブジェクト モデル)、SSL (Secure Sockets Layer)、または IPSec (インターネット プロトコル セキュリティ) を使用します。
  • データが暗号化されてデータベースに格納される場合は、通常、データ アクセス ロジック コンポーネントがそのデータの暗号化と復号化を担当します。 情報が公開される危険性が高い場合は、データ アクセス ロジック コンポーネントとの通信チャネルをセキュリティで保護することを真剣に検討してください。

ビジネス エンティティ コンポーネントでのセキュリティの推奨事項

ビジネス エンティティを (XML やデータセットなどの) データ構造として実装する場合は、セキュリティ チェックを実装する必要はありません。 ただし、ビジネス エンティティを CRUD 操作を備えたカスタム ビジネス エンティティ コンポーネントとして実装する場合は、以下の推奨事項を検討します。

  • エンティティが完全に信頼していないビジネス プロセスに公開される場合は、ビジネス エンティティ コンポーネントとデータ アクセス ロジック コンポーネントに承認チェックを実装します。 ただし、両方のレベルにチェックを実装すると、両方のセキュリティ ポリシーの同期を維持する保守上の問題点が生じることがあります。
  • ビジネス エンティティに通信セキュリティやデータの暗号化を扱わせるべきではありません。 このような作業は対応するデータ アクセス ロジック コンポーネントに担当させます。

配置

ここでは、データ アクセス ロジック コンポーネントとビジネス エンティティ コンポーネントを配置する方法の決定に役立つ推奨事項を示します。

データ アクセス ロジック コンポーネントを配置する

データ アクセス ロジック コンポーネントを配置するには次の 2 つの方法があります。

  • ビジネス プロセス オブジェクトと共にデータ アクセス ロジック コンポーネントを配置します。 この配置手法により、最適なパフォーマンスのデータ転送が提供され、その他次のようないくつか技術的な利点が得られます。
    • ビジネス プロセス オブジェクトとデータ アクセス ロジック コンポーネントの間を、トランザクションがシームレスに流れることができます。 ただし、トランザクションはリモート処理チャネルを経由してシームレスに流れることはありません。 リモート処理のシナリオでは、DCOM を使用してトランザクションを実装する必要があります。 さらに、ビジネス プロセスとデータ アクセス ロジックコンポーネントがファイアウォールで分離されていた場合は、DTC 通信を可能にするために、両方の物理層間でファイアウォール ポートを開く必要があります。
    • ビジネス プロセス オブジェクトとデータ アクセス ロジック コンポーネントを同時に配置することにより、トランザクションが失敗するノード数が減少します。
    • ビジネス プロセス オブジェクトとデータ アクセス ロジック コンポーネントの間を、セキュリティ コンテキストが自動的に流れます。 プリンシパル オブジェクトを設定する必要がありません。
  • ユーザー インターフェイス コードと共にデータ アクセス ロジック コンポーネントを配置します。 データ アクセス ロジック コンポーネントは、UI コンポーネントや UI プロセス コンポーネントから直接使用される場合があります。 Web シナリオでパフォーマンスを向上するために、データ アクセス ロジック コンポーネントを UI コードと共に配置できます。この配置方法により、UI 層は最適なパフォーマンスでデータ リーダー ストリーミングを利用できます。 ただし、この配置方法を検討する場合は、以下の点に注意してください。
    • UI コードと共にデータ アクセス ロジック コンポーネントを配置しない一般的な理由は、Web ファームからデータ ソースへの直接ネットワーク アクセスを防ぐことにあります。
    • DMZ シナリオで Web ファームが配置される場合は、SQL Server にアクセスするためにファイアウォール ポートを開く必要があります。 COM+ トランザクションを使用している場合は、DTC 通信用に新たなファイアウォール ポートを開く必要があります。 詳細については、「.NET データ アクセス アーキテクチャ ガイド」 を参照してください。

ビジネス エンティティを配置する

ビジネス エンティティは、アプリケーションの多くの異なる層で使用されます。 アプリケーションが複数の物理層に及ぶ場合、ビジネス エンティティを実装する方法によっては、複数の場所にビジネス エンティティを配置する必要が生じることがあります。 以下の一覧は、さまざまな実装のシナリオでビジネス エンティティを配置する方法を説明しています。

  • 型指定されたデータセットとしてビジネス エンティティを配置します。 型指定されたデータセット クラスには、データ アクセス ロジック コンポーネントおよび呼び出し側アプリケーションによってアクセスされる必要があります。 そのため、複数層に配置される共通アセンブリで型指定されたデータセット クラスを定義することをお勧めします。
  • カスタム ビジネス エンティティ コンポーネントとして実装されるビジネス エンティティを配置します。 データ アクセス ロジック コンポーネントでメソッド シグニチャを定義する方法によっては、データ アクセス ロジック コンポーネントによりカスタム エンティティ クラスにアクセスする必要がある場合があります。 複数層に配置される共通アセンブリでカスタム エンティティ クラスを定義することによって、型指定されたデータセットの場合と同じ推奨事項に従います。
  • 汎用データセットまたは XML 文字列として実装されるビジネス エンティティを配置します。 汎用データセットと XML 文字列は、個別のデータ型を表現しません。 このような形式で実装されるビジネス エンティティでは配置の問題は生じません。

付録

データ アクセス ロジック コンポーネント クラスを定義する方法
データのコレクションと階層を表現するために XML を使用する方法
.NET アプリケーションでプログラムからスタイル シートを適用する方法
型指定されたデータセットを作成する方法
ビジネス エンティティ コンポーネントを定義する方法
ビジネス エンティティ コンポーネント内のデータのコレクションと階層を表現する方法
ビジネス エンティティ コンポーネントをユーザー インターフェイス コントロールに連結する方法
ビジネス エンティティ コンポーネントでイベントを公開する方法
ビジネス エンティティ コンポーネントを XML 形式にシリアル化する方法
ビジネス エンティティ コンポーネントを SOAP 形式にシリアル化する方法
ビジネス エンティティ コンポーネントをバイナリ形式にシリアル化する方法

データ アクセス ロジック コンポーネント クラスを定義する方法

以下のコードは、CustomerDALC というクラスの簡単な定義です。このクラスは Customer ビジネス エンティティのデータ アクセス ロジック コンポーネント クラスです。 CustomerDALC クラスにより、Customer ビジネス エンティティの CRUD 操作が実装され、このオブジェクトのビジネス ロジックをカプセル化する追加メソッドが提供されます。


public class CustomerDALC 
{
  private string conn_string;

  public CustomerDALC()
  {
    // セキュリティで保護された場所または暗号化された場所から接続文字列を取得し、
    // それを conn_string に代入します
  }

  public CustomerDataSet GetCustomer(string id)
  {
    // 顧客データを保持する型指定されたデータセットを取得するコード
  }

  public string CreateCustomer(string name,
                               string address, string city, string state, string zip)

  {
    // このメソッドに渡されるスカラ パラメータに基づいて、データベースに
    // 新しい顧客を作成するコード
    // このメソッドから customerID を返します
  }

  public void UpdateCustomer(CustomerDataSet updatedCustomer)
  {
    // CustomerDataSet 型のパラメータとして送信される顧客データに基づいて、
    // データベースを更新するコード
  }

  public void DeleteCustomer(string id)
  {
    // 指定された ID で顧客を削除するコード
  }

  public DataSet GetCustomersWhoPurchasedProduct(int productID)
  {
    // このメソッドが顧客に関係付けられたすべての情報を取得しないので、
    // 汎用のデータセットを使用して顧客を取得するコード
  }
}

データのコレクションと階層を表現するために XML を使用する方法

以下の例では、XML ドキュメントでデータのコレクションと階層を表現する方法を示しています。 XML ドキュメントにより、顧客が行った 1 つの注文が表現されます。<OrderDetails> 要素により、その注文の注文明細情報のコレクションが保持されることに注意してください。


<Order xmlns="urn:aUniqueNamespace">
  <OrderID>10248</OrderID>
  <CustomerID>VINET</CustomerID>
  <OrderDate>1996-07-04</OrderDate>
  <ShippedDate>1996-07-16</ShippedDate>
  <OrderDetails>
    <OrderDetail>
      <ProductID>11</ProductID>
      <UnitPrice>14.00</UnitPrice>
      <Quantity>12</Quantity>
    </OrderDetail>
    <OrderDetail>
      <ProductID>42</ProductID>
      <UnitPrice>9.80</UnitPrice>
      <Quantity>10</Quantity>
    </OrderDetail>
    <OrderDetail>
      <ProductID>72</ProductID>
      <UnitPrice>34.80</UnitPrice>
      <Quantity>5</Quantity>
    </OrderDetail>
  </OrderDetails>
</Order>

.NET アプリケーションでプログラムからスタイル シートを適用する方法

.NET アプリケーションでプログラムからスタイル シートを適用するには、以下の手順を実行します。

  1. 以下のコードで示すように、System.Xml.Xsl 名前空間をインポートします。 System.Xml.Xsl 名前空間は、.NET Framework クラス ライブラリの XSLT 変換クラスを保持します。

    
    using System.Xml.Xsl;
    
  2. 以下のコードで示すように、XslTransform オブジェクトを作成します。

    
    XslTransform stylesheet = new XslTransform();
    
  3. 以下のコードで示すように、必要なスタイル シートを XslTransform オブジェクトに読み込みます。

    
    stylesheet.Load("MyStylesheet.xsl");
    
  4. 以下のコードで示すように、XslTransform オブジェクトの Transform メソッドを呼び出します。 Transform メソッドの呼び出し時に、XML ソース ドキュメントと結果ドキュメントの名前を指定します。

    
    stylesheet.Transform(sourceDoc, resultDoc);
    

型指定されたデータセットを作成する方法

ビジネス エンティティを表現するために、型指定されたデータセットを使用できます。 型指定されたデータセットを作成するには、次のようないくつかの方法があります。

  • Microsoft Visual Studio® .NET 内でデータ アダプタから作成する
  • Visual Studio .NET 内で XSD スキーマ ファイルから作成する
  • XSD スキーマ定義ツール (xsd.exe) を使用して、.NET Framework コマンド プロンプト ウィンドウから作成する

   データセットの構造を表現するために、DataSet から継承し、メソッド、プロパティ、および入れ子になったクラスを定義することによって、型指定されたデータセットをプログラムで定義することもできます。 これを行う最も簡単な方法は、以下の手順の 1 つを使って型指定されたデータセットを作成し、その後、この型指定されたデータセット クラスをそれ以降の独自の型指定されたデータセットの基礎として使用することです。

データ アダプタを使用して型指定されたデータセットを作成する

データ アダプタを使用して型指定されたデータセットを作成するには、以下の手順を実行します。

  1. Visual Studio .NET で、フォームまたはコンポーネントにデータ アダプタを追加します。 データ アダプタ構成ウィザードで、データ アダプタの接続情報を指定します。 また、必要に応じて、Select コマンド、Insert コマンド、Update コマンド、および Delete コマンド用の SQL 文字列またはストアド プロシージャも指定します。
  2. コンポーネント デザイナで、データ アダプタ オブジェクトを右クリックし、[データセットの生成] をクリックします。
  3. [データセットの生成] ダイアログ ボックスで、[新規作成] をオンし、新しい DataSet クラスの名前を入力して [OK] をクリックします。
  4. 型指定されたデータセットが作成されたことを確認するには、ソリューション エクスプローラで [すべてのファイルを表示] ボタンをクリックします。 XSD スキーマ ファイルのノードを展開し、XSD スキーマに関連付けられたコード ファイルが存在することを確認します。 そのコード ファイルにより、新しい型指定された DataSet クラスが定義されます。

XSD スキーマ ファイルから型指定されたデータセットを作成する

Visual Studio .NET を使用して XSD スキーマ ファイルから型指定されたデータセットを作成するには、以下の手順を実行します。

  1. Visual Studio .NET で新しいプロジェクトを作成するか、既存のプロジェクトを開きます。
  2. プロジェクトに既存の XSD スキーマを追加するか、コンポーネント デザイナで新しい XSD スキーマを作成します。
  3. ソリューション エクスプローラで XSD スキーマ ファイルをダブルクリックし、コンポーネント デザイナにその XSD スキーマを表示します。
  4. コンポーネント デザイナで主要な XSD スキーマ要素を選択します。
  5. [スキーマ] メニューの [データセットの生成] をクリックします。
  6. 型指定されたデータセットが作成されたことを確認するには、ソリューション エクスプローラで [すべてのファイルを表示] ボタンをクリックします。 XSD スキーマ ファイルのノードを展開し、XSD スキーマに関連付けられたコード ファイルが存在することを確認します。 そのコード ファイルにより、新しい型指定された DataSet クラスが定義されます。

XSD スキーマ定義ツール(xsd.exe) を使用して型指定されたデータセットを作成する

XML スキーマ定義ツールにより、XSD スキーマ ファイル、XDR スキーマ ファイル、または XML インスタンス ドキュメントから型指定されたデータセットを作成できます。 以下のコマンドは、XsdSchemaFile.xsd という XSD スキーマ ファイルを使用して、カレント ディレクトリの XsdSchemaFile.cs という Visual C# ソース ファイルに型指定されたデータセットを生成します。


xsd /dataset /language:C# XsdSchemaFile.xsd

詳細については、「厳密に型指定された DataSet の生成」を参照してください。

ビジネス エンティティ コンポーネントを定義する方法

以下の例では、Product ビジネス エンティティのカスタム エンティティ クラスを定義する方法を示しています。


public class ProductEntity
{
  // Product エンティティの状態を保持するためのプライベート フィールド
  private int productID;
  private string productName;
  private string quantityPerUnit;
  private decimal unitPrice;
  private short unitsInStock;
  private short unitsOnOrder;
  private short reorderLevel;

  // エンティティの状態を公開するためのパブリック プロパティ
  public int ProductID
  {
    get { return productID; }
    set { productID = value; }
  }
  public string ProductName
  {
    get { return productName; }
    set { productName = value; }
  }
  public string QuantityPerUnit
  {
    get { return quantityPerUnit; }
    set { quantityPerUnit = value; }
  }
  public decimal UnitPrice
  {
    get { return unitPrice; }
    set { unitPrice = value; }
  }
  public short UnitsInStock
  {
    get { return unitsInStock; }
    set { unitsInStock = value; }
  }
  public short UnitsOnOrder
  {
    get { return unitsOnOrder; }
    set { unitsOnOrder = value; }
  }
  public short ReorderLevel
  {
    get { return reorderLevel; }
    set { reorderLevel = value; }
  }

  // ローカライズされた処理を実行するためのメソッドとプロパティ
  public void IncreaseUnitPriceBy(decimal amount)
  {
    unitPrice += amount;
  }
  public short UnitsAboveReorderLevel
  {
    get { return (short)(unitsInStock - reorderLevel); }
  }
  public string StockStatus
  {
    get
    { 
      return "In stock: " + unitsInStock + ", on order: " + unitsOnOrder;
    }
  }
}

ビジネス エンティティ コンポーネント内のデータのコレクションと階層を表現する方法

以下の例では、Order ビジネス エンティティのカスタム エンティティ クラスを定義する方法を示しています。 各注文は、多くの注文明細から構成されます。これらの注文明細は、OrderEntity クラスのデータセットに格納されます。


public class OrderEntity
{
  // 注文情報を保持するためのプライベート フィールド
  private int orderID;
  private string customerID;
  private DateTime orderDate;
  private DateTime shippedDate;

  // 注文明細情報を保持するためのプライベート フィールド
  private DataSet orderDetails;

  // 注文情報を公開するためのパブリック プロパティ
  public int OrderID
  {
    get { return orderID; }
    set { orderID = value; }
  }
  public string CustomerID
  {
    get { return customerID; }
    set { customerID = value; }
  }
  public DateTime OrderDate
  {
    get { return orderDate; }
    set { orderDate = value; }
  }
  public DateTime ShippedDate
  {
    get { return shippedDate; }
    set { shippedDate = value; }
  }

  // 注文明細情報を公開するためのパブリック プロパティ
  public DataSet OrderDetails
  {
    get { return orderDetails; }
    set { orderDetails = value; }
  }

  // 注文明細情報に簡単にアクセスするための追加メソッド
  public bool IsProductOrdered(int productID)
  {
    // DataTable に主キー列を定義する必要があります
    DataRow row = orderDetails.Tables[0].Rows.Find(productID);
    
    if (row != null)
  return true;
    else
  return false;
  }

  // 注文明細情報に簡単にアクセスするための追加プロパティ
  public int NumberOfOrderItems
  {
    get
    {
      return orderDetails.Tables[0].Rows.Count;
    }
  }
} 

OrderEntity クラスでは、以下の点に注意してください。

  • クラスには、注文に関する情報を保持するためのプライベート フィールドがあります。 また、注文に関する詳しい明細を保持するためのプライベート データセット フィールドもあります。 データ アクセス ロジック コンポーネントにより、OrderEntity オブジェクトが作成されるときに、このようなフィールドがすべて設定されます。
  • クラスには、注文に関する情報を公開するためのパブリック プロパティがあります。 また、呼び出し側アプリケーションが注文明細にアクセスできるようにするために、データセットを公開するプロパティもあります。
  • クラスには、注文明細に簡単にアクセスできるようにする、追加のメソッドとプロパティがあります。
    • IsProductOrdered メソッドは、ProductID パラメータを受け取り、その製品が注文に存在するかどうかを示すブール値を返します。
    • NumberOfOrderItems プロパティにより、注文内の注文明細数が示されます。

ビジネス エンティティ コンポーネントをユーザー インターフェイス コントロールに連結する方法

Windows フォーム アプリケーションおよび ASP.NET アプリケーションで、ユーザー インターフェイス コントロールをカスタム エンティティに連結できます。 この場合、可能なシナリオには次の 2 つがあります。

  • 1 つのビジネス エンティティをユーザー インターフェイス コントロールに連結します。 以下のコード サンプルでは、OrderDALC オブジェクトから OrderEntity オブジェクトを取得し、OrderEntity オブジェクトを Windows フォームのコントロールに連結する方法を示しています。 ユーザーがこのようなコントロールの値を変更すると、元になる OrderEntity オブジェクト内のデータも自動的に変更されます。

    
    // OrderDALC オブジェクトを作成します
    OrderDALC dalcOrder = new OrderDALC();
    
    // dalcOrder を使用して、注文 ID 10248 の OrderEntity オブジェクトを取得します
    // このコードは OrderDALC クラスに GetOrder() という名前のメソッドがあることを前提としています
    // GetOrder() は、特定の注文 ID の OrderEntity オブジェクトを返します
    OrderEntity order = dalcOrder.GetOrder(10248);
    
    // OrderEntity の OrderID プロパティを TextBox コントロールに連結します
    textBox1.DataBindings.Add("Text", order, "OrderID");
    
    // OrderEntity の CustomerID プロパティを別の TextBox コントロールに連結します
    textBox2.DataBindings.Add("Text", order, "CustomerID");
    
    // OrderEntity の OrderDate プロパティを DatePicker コントロールに連結します
    dateTimePicker1.DataBindings.Add("Value", order, "OrderDate");
    
    // OrderEntity の ShippedDate プロパティを別の DatePicker コントロールに連結します
    dateTimePicker2.DataBindings.Add("Value", order, "ShippedDate");
    
    // OrderEntity の OrderDetails データセットを DataGrid コントロールに連結します
    // DataGrid は、データセットの各 DataRow をグリッド内の個別の行に表示します
    dataGrid1.DataSource = order.OrderDetails.Tables[0].DefaultView;
    

    準備が整ったら、変更された OrderEntity オブジェクトを OrderDALC に戻すことができ、以下のコードで示すように、データをデータベースに保存できます。

    
    // dalcOrder 経由で OrderEntity オブジェクトをデータベースに再保存します
    // このコードは OrderDALC クラスに UpdateOrder() という名前のメソッドがあることを前提としています
    // UpdateOrder() は OrderEntity パラメータを受け取り、データベース内の対応するエントリを更新します
    dalcOrder.UpdateOrder(order);
    
  • ビジネス エンティティのコレクションを DataGrid コントロールに連結します。 以下のコード サンプルは、OrderDALC から OrderEntity オブジェクトの配列を取得し、取得した配列を Windows フォームの DataGrid コントロールに連結する方法を示しています。 DataGrid は、各配列要素 (つまり、各 OrderEntity オブジェクト) をグリッド内の個別の行に表示します。

    
    // OrderDALC オブジェクトを作成します
    OrderDALC dalcOrder = new OrderDALC();
    
    // dalcOrder を使用して、顧客 "VINET" の OrderEntity オブジェクトの配列を取得します
    // このコードは OrderDALC クラスに GetOrdersForCustomer() という名前のメソッドがあることを前提としています
    // GetOrdersForCustomer() は、特定の顧客の OrderEntity オブジェクトの配列を返します
    OrderEntity[] orderEntities = dalcOrder.GetOrdersForCustomer("VINET");
    
    // 配列を DataGrid コントロールに連結します
    dataGrid1.DataSource = orderEntities;
    

    準備が整ったら、変更された配列を OrderDALC に戻すことができ、以下のコードで示すように、データをデータベースに保存できます。

    
    // dalcOrder 経由で OrderEntity オブジェクトをデータベースに再保存します
    // このコードは OrderDALC クラスに UpdateOrders() という名前のメソッドがあることを前提としています
    // UpdateOrders() は、OrderEntity オブジェクトの配列を受け取り、データベース内の対応するエントリを更新します
    dalcOrder.UpdateOrders(orderEntities);
    

ビジネス エンティティ コンポーネントでイベントを公開する方法

カスタム エンティティでは、ビジネス エンティティの状態が変化したときに、イベントを発生できます。 データを表示する場所にかかわらず、データを最新状態に更新できるので、リッチ クライアント ユーザー インターフェイスの設計では、このようなイベントが役に立ちます。 以下のコード サンプルでは、OrderEntity クラスでビジネス エンティティ関連のイベントを発生する方法を示しています。


// すべてのビジネス エンティティ イベントの共通イベント クラスを定義します
public class EntityEventArgs : EventArgs
{
  // イベントに関する情報を提供するイベント メンバを定義します
}

// ビジネス エンティティ関連のイベントのシグニチャを指定するデリゲートを定義します
public delegate void EntityEventHandler(Object source, EntityEventArgs e);

// ビジネス エンティティの状態が変化するときにイベントを発生するカスタム エンティティ クラスを定義します
public class OrderEntity
{
  // ビジネス エンティティの状態の変化 '前' と '後' のイベントを定義します
  public event EntityEventHandler BeforeChange, AfterChange;

  // ビジネス エンティティの状態を保持するためのプライベート フィールド
  private int orderID;
  private int customerID;
  private DateTime orderDate;
  private DateTime shippedDate;
  private DataSet orderDetails;

  // ビジネス エンティティの状態を公開するためのパブリック プロパティ
  public int OrderID
  {
    get { return orderID; }
    set
    { 
      BeforeChange(this, new EntityEventArgs());   // '変化前' のイベントを発生します
      orderID = value;
      AfterChange(this, new EntityEventArgs());    // '変化後' のイベントを発生します
    }
  }
  public int CustomerID
  {
    get { return customerID; }
    set
    { 
      BeforeChange(this, new EntityEventArgs());   // '変化前' のイベントを発生します
      customerID = value;
      AfterChange(this, new EntityEventArgs());    // '変化後' のイベントを発生します
    }
  }
  public DateTime OrderDate
  {
    get { return orderDate; }
    set
    {
      BeforeChange(this, new EntityEventArgs());   // '変化前' のイベントを発生します
      orderDate = value;
      AfterChange(this, new EntityEventArgs());    // '変化後' のイベントを発生します
    }
  }
  public DateTime ShippedDate
  {
    get { return shippedDate; }
    set
    {
      BeforeChange(this, new EntityEventArgs());   // '変化前' のイベントを発生します
      shippedDate = value;
      AfterChange(this, new EntityEventArgs());    // '変化後' のイベントを発生します
    }
  }

  // 必要に応じてメンバを追加します...
}

上記のコードでは、以下の点に注意してください。

  • EntityEvent クラスにより、ビジネス エンティティ関連のイベントに関する情報が提供されます。 EntityEventHandler デリゲートにより、カスタム エンティティ クラスによって発生されるビジネス エンティティ関連のすべてのイベントのシグニチャが指定されます。 デリゲート シグニチャは、イベント ハンドラ デリゲートの .NET Framework の推奨ガイドラインに従います。 .NET でイベントを定義および使用する方法のガイドラインについては、「イベントの使用方法のガイドライン」を参照してください。
  • OrderEntity クラスにより、BeforeChange と AfterChange という 2 つのイベントが定義されます。
  • OrderEntity のプロパティ設定関数は、ビジネス エンティティの状態が変化する前に BeforeChange イベントを、ビジネス エンティティの状態が変化した後に AfterChange イベントを発生します。

ビジネス エンティティ コンポーネントを XML 形式にシリアル化する方法

ここでは、以下の項目について説明されています。

  • カスタム エンティティ オブジェクトをシリアル化するための XmlSerializer の使用
  • XML Web サービスでのオブジェクトの XML シリアル化
  • シリアル化されたカスタム エンティティ オブジェクトの既定の XML 形式
  • シリアル化されたカスタム エンティティ オブジェクトの XML 形式の制御

カスタム エンティティ オブジェクトをシリアル化するための XmlSerializer の使用

以下のコード サンプルでは、XmlSerializer クラスを使用して OrderEntity オブジェクトを XML 形式にシリアル化する方法を示しています。


using System.Xml.Serialization;     // この名前空間は XmlSerializer クラスを保持します
...
// OrderEntity 型のオブジェクトをシリアル化するために、XmlSerializer オブジェクトを作成します
XmlSerializer serializer = new XmlSerializer(typeof(OrderEntity));

// OrderEntity オブジェクトを "MyXmlOrderEntity.xml" という名前の XML ファイルにシリアル化します
TextWriter writer = new StreamWriter("MyXmlOrderEntity.xml");
serializer.Serialize(writer, order);
writer.Close();

XML Web サービスでのオブジェクトのシリアル化

以下のコード サンプルでは、カスタム エンティティ オブジェクトを使用する XML Web サービスを記述する方法を示しています。


namespace MyWebService
{
  [WebService(Namespace="urn:MyWebServiceNamespace")]
  public class OrderWS : System.Web.Services.WebService
  {
    [WebMethod]
    public OrderEntity GetOrder(int orderID)
    {
     // OrderDALC オブジェクトを作成します
     OrderDALC dalcOrder = new OrderDALC();

     // dalcOrder を使用して、指定された注文 ID の OrderEntity オブジェクトを取得します
     // このコードは OrderDALC クラスに GetOrder という名前のメソッドがあることを前提としています
     // GetOrder メソッドはパラメータとして注文 ID を受け取り、OrderEntity オブジェクトを返します
     // OrderEntity オブジェクトがこの注文のすべてのデータを保持します
     OrderEntity order = dalcOrder.GetOrder(10248);

      // OrderEntity オブジェクトを返します。オブジェクトは自動的にシリアル化されます。
      return order;
    }

    [WebMethod]
    public void UpdateOrder(OrderEntity order)
    {
     // OrderDALC オブジェクトを作成します
     OrderDALC dalcOrder = new OrderDALC();

     // dalcOrder を使用して OrderEntity オブジェクトのデータをデータベースに保存します
     // このコードは OrderDALC クラスに UpdateOrder という名前のメソッドがあることを前提としています
     // UpdateOrder メソッドは、OrderEntity オブジェクトを受け取り、そのデータをデータベースに保存します
    dalcOrder.UpdateOrder(order);
    }

上記のコードでは、以下の点に注意してください。

  • GetOrder メソッドはパラメータとして注文 ID を受け取り、この注文に関するデータを保持する OrderEntity オブジェクトを返します。
  • UpdateOrder メソッドは OrderEntity オブジェクトを受け取り、オブジェクトのデータをデータベースに保存します。
  • クライアント アプリケーションが GetOrder メソッドと UpdateOrder メソッドを呼び出すと、そのメソッド呼び出しの OrderEntity オブジェクトが自動的に XML 形式にシリアル化されます。

シリアル化されたカスタム エンティティ オブジェクトの既定の XML 形式

以下の XML ドキュメントは、OrderEntity オブジェクトの既定の XML シリアル化形式を示しています。


<?xml version="1.0" encoding="utf-8"?>
<OrderEntity xmlns:xsd="http://www.w3.org/2001/XMLSchema"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <OrderID>10248</OrderID>
  <CustomerID>VINET</CustomerID>
  <OrderDate>1996-07-04T00:00:00.0000000+01:00</OrderDate>
  <OrderDetails> ... see below ... </OrderDetails>
  <ShippedDate>1996-07-16T00:00:00.0000000+01:00</ShippedDate>
</OrderEntity>

上記のドキュメントは、以下の XML シリアル化の既定の規則を示しています。

  • XML ドキュメントのルート要素は、クラス名である OrderEntity になります。
  • OrderEntity オブジェクトの各パブリック プロパティ (およびフィールド) は、同じ名前を持つ要素にシリアル化されます。

OrderEntity の OrderDetails プロパティはデータセットです。 データセットは、XML シリアル化に関する組み込みサポートを提供します。 OrderDetails データセットは、以下のようにシリアル化されます。


<OrderDetails>
  <xs:schema id="NewDataSet" 
             xmlns:xs="http://www.w3.org/2001/XMLSchema"
             xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:Locale="en-
      UK">
      <xs:complexType>
        <xs:choice maxOccurs="unbounded">
          <xs:element name="OrderDetails">
            <xs:complexType>
              <xs:sequence>
                <xs:element name="OrderID" type="xs:int" minOccurs="0" />
                <xs:element name="ProductID" type="xs:int" minOccurs="0" />
                <xs:element name="UnitPrice" type="xs:decimal" minOccurs="0"
                  />
                <xs:element name="Quantity" type="xs:short" minOccurs="0" />
              </xs:sequence>
            </xs:complexType>
          </xs:element>
        </xs:choice>
      </xs:complexType>
    </xs:element>
  </xs:schema>
  <diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
                   xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1">
    <NewDataSet>
      <OrderDetails diffgr:id="OrderDetails1" msdata:rowOrder="0"
                    diffgr:hasChanges="inserted">
        <OrderID>10248</OrderID>
        <ProductID>11</ProductID>
        <UnitPrice>14</UnitPrice>
        <Quantity>12</Quantity>
      </OrderDetails>
     <OrderDetails diffgr:id="OrderDetails2" msdata:rowOrder="1"
                   diffgr:hasChanges="inserted">
        <OrderID>10248</OrderID>
        <ProductID>42</ProductID>
        <UnitPrice>9.8</UnitPrice>
        <Quantity>10</Quantity>
      </OrderDetails>
      <OrderDetails diffgr:id="OrderDetails3" msdata:rowOrder="2"
                    diffgr:hasChanges="inserted">
        <OrderID>10248</OrderID>
        <ProductID>72</ProductID>
        <UnitPrice>34.8</UnitPrice>
        <Quantity>5</Quantity>
      </OrderDetails>
    </NewDataSet>
  </diffgr:diffgram>
</OrderDetails>

データセットのシリアル化では、以下の点に注意してください。

  • <xs:schema> セクションは、テーブル、列名、列の型などのデータセットの構造を記述します。
  • <xs:diffgram> セクションはデータセットのデータを保持します。 各 <OrderDetails> 要素は、データセットの OrderDetails テーブル内の独立した行を表します。

シリアル化されたカスタム エンティティ オブジェクトの XML 形式の制御

カスタム エンティティ クラスで .NET 属性を使用して、プロパティやフィールドを XML にシリアル化する方法を制御できます。 以下のように改定した OrderEntity クラスを考えます。


 [XmlRoot(ElementName="Order", Namespace="urn:MyNamespace")]
public class OrderEntity
{
  [XmlAttribute(AttributeName="ID")]
  public int OrderID { ...前述のように取得コードと設定コードを用意します... }

  [XmlAttribute(AttributeName="CustID")]
  public string CustomerID { ...前述のように取得コードと設定コードを用意します... }

  [XmlElement(ElementName="Ordered")]
  public DateTime OrderDate { ...前述のように取得コードと設定コードを用意します... }

  public DataSet OrderDetails { ...前述のように取得コードと設定コードを用意します... }

  [XmlElement(ElementName="Shipped")
  public DateTime ShippedDate { ...前述のように取得コードと設定コードを用意します... }

  // 必要に応じてメンバを追加します...
}
  • OrderEntity オブジェクトを XML にシリアル化すると、以下のような形式になります。

<?xml version="1.0" encoding="utf-8"?>
<Order ID="10248"
       CustID="VINET"
       xmlns="urn:MyNamespace"
       xmlns:xsd="http://www.w3.org/2001/XMLSchema"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Ordered>1996-07-04T00:00:00.0000000+01:00</Ordered>
  <OrderDetails> ...details same as before... </OrderDetails>
  <Shipped>1996-07-16T00:00:00.0000000+01:00</Shipped>
</Order>

XML シリアル化を制御するための属性の使い方の詳細については、「XML シリアル化を制御する属性」を参照してください。

ビジネス エンティティ コンポーネントを SOAP 形式にシリアル化する方法

以下のコード サンプルでは、SoapFormatter クラスを使用して OrderEntity オブジェクトを SOAP 形式にシリアル化する方法を示しています。 SOAP プロトコルを使用して XML Web サービスとの間でオブジェクトを受け渡しするとき、および HTTP リモート処理チャネルを使用してリモート処理サーバーとの間でオブジェクトを受け渡しするときにも、(暗黙に) SOAP シリアル化が行われます。 さらに、TCP リモート処理チャネルをするときに、SOAP 形式を指定できます。


using System.Runtime.Serialization.Formatters.Soap;    // SoapFormatter クラス用
...
// OrderEntity 型のオブジェクトをシリアル化するために、SoapFormatter オブジェクトを作成します
SoapFormatter formatter = new SoapFormatter();

// OrderEntity オブジェクトを "MySoapOrderEntity.xml" という名前の  SOAP (XML) ファイルにシリアル化します
FileStream stream = File.Create("MySoapOrderEntity.xml");
formatter.Serialize(stream, order);
stream.Close();

カスタム エンティティ オブジェクトで SOAP シリアル化を使用する場合は、以下のコードに示すように、Serializable 属性を使用することによって、エンティティ クラスに注釈を付ける必要があります。


 [Serializable]
public class OrderEntity
{
  // 上記のようにメンバを定義します...

シリアル化中に生成される SOAP 形式をカスタマイズする場合は、エンティティ クラスに ISerializable インターフェイスを実装する必要があります。 シリアル化中に呼び出すために SoapFormatter クラスに GetObjectData メソッドを用意する必要があります。また、シリアル化解除中に呼び出してオブジェクトを再作成するために、SoapFormatter クラスに特殊なコンストラクタを用意する必要があります。 以下のコードでは、ISerializable インターフェイス、GetObjectData メソッド、および特殊コンストラクタの使い方を例示しています。


using System.Runtime.Serialization;   // ISerializable インターフェイス、および関連する型用
...
[Serializable]
public class OrderEntity : ISerializable
{
  // シリアル化中に SoapFormatter によって呼び出されるシリアル化関数
  void ISerializable.GetObjectData(SerializationInfo info, StreamingContextctxt)

  {
    // 各フィールドを SerializationInfo オブジェクトに追加します
    info.AddValue("OrderID", orderID);
    // 必要に応じてコードを追加します...
  }

  // シリアル化解除中に SoapFormatter によって呼び出されるシリアル化解除コンストラクタ
  public OrderEntity(SerializationInfo info, StreamingContext ctxt)
  {
    // SerializationInfo オブジェクトから OrderEntity フィールドにシリアル化解除します
    orderID = (int)info.GetValue("OrderID", typeof(int));
    // 必要に応じてコードを追加します...
  }
  
  // 上記のようにその他のメンバを定義します...
}

SOAP シリアル化の詳細については、「基本的なシリアル化」を参照してください。

ビジネス エンティティ コンポーネントをバイナリ形式にシリアル化する方法

以下のコード サンプルでは、BinaryFormatter クラスを使用して OrderEntity オブジェクトをバイナリ形式にシリアル化する方法を示しています。 バイナリ シリアル化は、TCP リモート処理チャネルを使用して、リモート処理サーバーとオブジェクトを受け渡しするときにも (暗黙に) 行われます。 さらに、HTTP リモート処理チャネルを使用するときに、パフォーマンスを向上するために、バイナリ形式への変換を指定できます。


using System.Runtime.Serialization.Formatters.Binary;    // BinaryFormatter クラス用
...
// OrderEntity 型のオブジェクトをシリアル化するために、BinaryFormatter オブジェクトを作成します
BinaryFormatter formatter = new BinaryFormatter();

// OrderEntity オブジェクトを "MyBinaryOrderEntity.dat" という名前のバイナリ ファイルにシリアル化します
FileStream stream = File.Create("MyBinaryOrderEntity.dat");
formatter.Serialize(stream, order);
stream.Close();

カスタム エンティティ オブジェクトでバイナリ シリアル化を使用する場合は、Serializable 属性を使用することによって、カスタム エンティティ クラスに注釈を付ける必要があります。 カスタム エンティティ クラスは、シリアル化中に生成されるバイナリ形式をカスタマイズするために、ISerializable インターフェイスを実装する必要があります。 どちらのシナリオのコーディング詳細も、SOAP シリアル化のためのコーディング詳細と同じです。

バイナリ シリアル化の詳細については、「バイナリ シリアル化」を参照してください。

共同執筆者

以下の共同執筆者とレビューアに感謝します。 Luca Bolognese、Mike Pizzo、Keith Short、Martin Petersen-Frey (PSS)、Pablo De Grande、Bernard Chen (Sapient)、Dimitris Georgakopoulos (Sapient)、Kenny Jones、Chris Brooks、Lance Hendrix、Pradyumna Siddhartha、Franco A. Ceruti (VBNext)、Diego Gonzalez (Lagash)、Chris Schoon。

また、次のコンテンツ チームにも感謝します。 Chris Sfanos、Angela Crocker、Andy Olsen、Sharon Smith。

Patterns and Practices ホーム