Silverlight をインストールするには、ここをクリックします*
Japan変更|すべてのMicrosoft のサイト
MSDN
|MSDN ライブラリ|デベロッパー センター|ダウンロード情報|開発ツール製品|コミュニティ|ご意見・ご要望|サイトマップ
MSDN Home > 連載コラム > Diving Into Data Access > データベースのようなデータ コンテナ

データベースのようなデータ コンテナ

Dino Esposito
Microsoft Corporation

April 26, 2001
日本語版最終更新日 2001年6月25日

最近、ADO.NET を一般大衆に紹介するという、いくぶん伝道師のような仕事に 打ち込みました。私は ADO.NET オブジェクト モデルに本来備わっている美しさについ て議論しているとき、元になったオブジェクト モデルである ADO に直感的に似ていると 感じてしまう超えがたいハードルが存在することに気付きました。

大多数の質問は、ADO.NET での重要な機能強化部分に集中します。

非接続モデルのコンピュータ処理 ? 「Web アプリケーションで高度なスケーラビリティを保証するモデルのことですか ? そんなのは、既に ADO 2.0 の頃から利用できていましたよ。」

XML との緊密な統合 ? 「冗談を言っているのですか ? XML と ADO との統合は ADO 2.1 で結び付き、 ADO 2.5 でその結び付きが強化されました。実際、これは非常に成功した結合だっ たと思いますが。」

フレームワークの残りの部分とのシームレスな統合 ? 「どの種類のフレームワークのことを話しているのですか ? COM ですか、COM+ ですか、 おそらく MFC のことを言っているのですね ?」

とはいうものの、"フレームワーク" という言葉は、ときには ADO.NET に関するプレゼン テーションを取り囲み、歓迎していた "冷静な関心" ("熱烈な無関心" ではなく) の壁を 打ち破ることに成功した初めての試みです。

これは、開発者が一般的にデータ アクセスにまったく無関心であるとか、ADO.NET に 特別無関心であるという意味ではありません。むしろ、開発者は新しい驚異的な機能や、 既存のサービスの驚くべき強化について聞きたいと思っているようでした。ADO.NET を使っ てプログラミングするには、開発者はデータ プロバイダに対して作用する接続やコマンドを 使用することになります。開発者はレコードのスナップショットを取得し、ランタイムに対して 再接続と彼らが行った変更の送信を要求します。開発者はコンテンツを XML ストリーム に保存します。

従って当然ながら、人々は ADO.NET では実際に何が新しくなったかを訊ねることに なります。プレゼンテーションが進み、ADO.NET の覆いの下に隠されているものが明らかに なってくるにつれ、何も新しいものはないのではないかという意識下の感覚が徐々に増大し てきます。

繰り返しますが、ADO.NET のプレゼンタが容易に気付く、率直で揺るぎのない懐疑心 に、"フレームワーク"という言葉が確かに最初の亀裂を入れました。

ADO.NET は、それを取り巻く システム SDK (この場合は .NET Framework) に先例の ない統合を持っています。このようなシームレスな統合は、設計パターンや基本クラス ライブ ラリのタイプシステムから Web サービスや ASP.NET に至るまで一貫しています。.NET で作業 する限り、データ アクセスではプログラミング モデルとは無関係に、ADO.NET クラスが望ま しい手段です。

前回のコラムでは、データ行のセットをスクロールするために単純で、効果的な前方参 照カーソルに相当するデータ リーダーについて説明しました。今回のコラムでは DataSet オブジェクトにスポットライトをあてます。これは、非接続の XML ドリブンのデータ コンテナの 典型である .NET クラスです。

DataSet オブジェクトと Recordset オブジェクト

DataSet オブジェクトの最も正確な説明は、このコラムのタイトルにあります。 タイトルの前に "インメモリ" という修飾語を付ければ、さらに正確になるように思えます。 したがって、これらをすべてまとめると、DataSet オブジェクトは "インメモリのデー タベースのようなデータ コンテナ" と考えることができます。

DataSet は、OLE DB または管理されたプロバイダに対して実行されたクエ リ コマンドの結果として設定されます。構文や構造が異なるにもかかわらず、少なくとも最も 単純なコンテキストでは、ADO Recordset とほぼ同じ方法で DataSet オブジェ クトを使用することになります。オブジェクトを作成し、いくつか引数を設定し、その後特殊な コマンド (通常は SQL コマンド) を実行することによりデータを設定します。次に、レコードの テーブルでレコード ポインタを前後に移動し、レコードを更新または削除します。

ここまでは、ADO Recordset と実際に異なる点はありません。また、オブジェクト モデル での "革新" というべきものもありません。ADO.NET では Recordset オブジェクトがなくなっ たので、既存の ADO コードとの旧バージョンとの互換性が著しく損なわれました。しかし、 Recordset オブジェクトが単になくなっただけで、最終的にはデータ アクセス コンポーネントの デザインやパフォーマンスの大幅な最適化がもたらされます。

では、ADO Recordset オブジェクトと DataSet オブジェクトの類似 した側面を調査することから始めましょう。 これらのオブジェクトは共に非接続にでき、オブジェ クト自体を XML にシリアル化でき、非接続のデータを使用して手動でオブジェクトを作成で きます。さらに、両方ともデータ シェイピング、ローカルな並べ替え、およびバッチ更新をサポー トします。

同様に、多くの設計上の側面が両者を著しく異なるものにしています。Recordset がデー タベース中心の性質を持つのに対して、DataSet はデータ中心です。DataSets はメモリ内 でのみ機能します。DataSet が存在するとすぐに、データベースまたは一般的にはデータ ソー スで何かを行った結果を保持します。DataSet オブジェクトはテーブルのインメモリ リポジトリです。必要なテーブルの数と同数のオブジェクトが存在します。それに対して Recordset の場合は 1 つだけです。

さらに、DataSet はディスク ファイルの作成に必要な負担なしに、そのコンテン ツを XML にシリアル化できます。ADO では XML は単なる出力形式ですが、ADO.NET で は XML はオブジェクトの根本的な表現方法です。DataSet コンテンツの XML 表 現は、特殊なプロパティとメソッドのセットを使用してあらゆる時点で利用できます。XML DOM オブジェクトの奇妙なインスタンスを使って実行時に表現を構築するような方法ではありません。

DataSet は、XML 出力を作成するため、またはリモート ソースから再構築するために、 データの異なるスキーマを使用できます。幸いなことに、階層化 XML モデルやリレーショナル Recordset スキーマも DataSet オブジェクト コンテキスト内に共存しており、レコー ドのセットをナビゲーションする先例のない能力を提供しています。

最後に、DataSet オブジェクトは .NET の管理されたクラスのインスタンスで す。COM オブジェクトのインスタンスではありません。また、COM オートメーション オブジェク トのインスタンスでもありません。つまり、DataSet オブジェクトは、アプリケーション の全体的なスケーラビリティを考慮しないで、ASP.NET Session スロットに安全 に格納できます。さらに、オブジェクトに含まれるレコードのブロックは、途中にある企業ファ イヤウォールとは無関係に送信できます。これが可能なのは、.NET オブジェクトがスレッド セーフで、スレッド アフィニティを必要としないためで、さらに DataSet が既に HTTP およ びポート 80 を問題なく通過する XML ストリームになっているためです。

DataSet オブジェクトの構造

DataSet オブジェクトは Component から継承され、IListSource インターフェイスを実装するクラスです。Visual Basic.NET での宣言は以下のようになります。

Public Class DataSet
   Inherits Component
   Implements IListSource 

IListSource インターフェイスは IList から派生され、そのコンテンツ コードが接続するある種のデータ ソースに依存します。IListSource は、そのよう なコンテンツを公開するために、GetList というメソッドを 1 つ持ちます。 GetList は項目の IList ベースのコレクションを返します。IList は基本 ICollection インターフェイスから継承され、結果的にはすべての .NET リストの抽 象基本クラスになります。

最終的には、DataSet オブジェクトはデータを保持する、より特化されたオブ ジェクトのコレクションです。DataSet オブジェクト自体としては、相互関連する多くのテー ブルを 1 つの場所で管理する必要があるときに非常に役に立つすべてを包含する性質 と、指定した XML スキーマに従ってシリアル化する能力を提供します。

さらに、DataSet は完全にインメモリのオブジェクトです。このことについては、このような 役割に通常適用される長所と短所をすべて持っています。格納される情報の量が特に揮 発性が高くなく、頻繁にデータを最新状態に更新する必要があるほど急激に増加しない 限りは、アクセスが容易で、高速です。

DataSet はデータベースのように見えるコンテナです。操作するデータはすべてメモリ内 に保持され、インデックス、並べ替え、論理結合、制約の設定、およびデータの整合性 チェックなどのメソッドを適用します。DataSet オブジェクトが直接実装するこう いった拡張機能はわずかですが、統合や一貫性が .NET Framework 全体に広がって いるという全体的な印象を強めています。

使用可能な 2 つのコンストラクタと通常の new キーワードを使って DataSet オブジェクトを作成します。

DataSet data = new DataSet();
DataSet data = new DataSet("MyDataSet");

おわかりのように、2 つのコンストラクタの違いは新しく作成されるデータ コンテナ に割り当てる名前だけです。名前を省略するか、空白以外の名前にする必要が あります。名前を省略する場合は、前者のコンストラクタを使用します。この場合の 既定名は NewDataSet です。しかし、データセットの名前はオブジェクトの 用途ではそれほど重要な役割は果たしません。オブジェクトの名前が使用されて いることを確認できる唯一の場所は、DataSet のコンテンツを表示する XML ドキュ メントのルート タグの内部だけです。

DataSet オブジェクトの構成要素

次に、DataSets オブジェクトの構成要素、つまり実際にプログラミング モデルを 形成するサブ オブジェクトやコレクションを調べ、DataSet オブジェクトに データを設定するために使用できるさまざまな手法について調べてみましょう。

DataSet クラスは 3 つの主要なコレクションを含んでいます。

  • Tables

  • Relations

  • ExtendedProperties

Tables は外部ソースから取得しているデータの子テーブルをすべて集め、 明示的にそれらをリポジトリに追加します。Tables は DataTable オブジェクト を使用してレンダリングされます。テーブルが DataSet ファミリの一部になると、そのテー ブルは自動的に DataSet の XML スキーマを使用してシリアル化可能になります。 DataSet 内のすべてのテーブルは重複しない名前で特定されますが、インデックスを 使用してアクセスすることもできます。

リレーションは、それ以前に DataSet に追加された 2 つのテーブル間のリレーション シップを作成する論理 SQL JOIN ステートメントです。リレーションは、JOIN ステートメントが行うのとほぼ同じ方法で、共通する列の一致する値に基づいて 2 つの テーブルをリンクします。

DataSet リレーションと JOIN ステートメントの大きな違いは、リンクに含まれる 2 つ のテーブルは別個のままで、物理的にも論理的にも一体化されたテーブルが作成され ないことです。リレーションはプライマリ テーブルの行に追加された余分な列として確認 できます。この列の内容は、リンク先のテーブルの一致する行の配列です。

ExtendedProperties は、その構造とロジックが完全にユーザー責任である カスタム ユーザー情報のコレクションを取得します。このプロパティは、 ASP Session コレ クションや Application コレクションを使用するのと同じ方法で使用します。

data.ExtendedProperties.Add("refreshat", "12:00");

DataSet オブジェクトのその他のプロパティの中で、特に説明しておく必要が あるのは、DefaultView プロパティです。これは、現在 DataSet を形成している すべてのテーブルをフィルタ選択した独自のビューを表す DataSetView オブジェ クトを返します。この機能を使用して、DataSet コンテンツの複数の異なるビューを構築で きます。たとえば、ユーザーごとに異なるフィールドを表示するビューなどです。

DataSet のビューを設定するには、まず DataSet を作成し、値を設定します。次に、 その DataSet の参照をクラスのコンストラクタに渡すことにより、DataSetView オブジェクトの新しいインスタンスを作成します。

DataSet data = new DataSet();
// ここでデータを設定します
DataSetView dsv = new DataSetView(data);

ここまでは、2 つのオブジェクト間の関連付けを作成してきましたが、テーブル固有のビュー を作成するために必要な情報はまだ提供していません。これは、新しい種類のオブジェクト、 TableMapping オブジェクトを使用して行います。

テーブル マッピングとは、DataSetView ビューにテーブルを表示するために使用 するカスタム設定を定義することを意味します。基本的には、DataSet 内の任意のテーブルの 先頭で一種のマスクを適用します。このマスクは、自動的なフィルタ選択や並べ替え以外に、 フィールドの代替名の設定も提供します。単純に DataSetView オブジェクトを DataSet の DefaultView プロパティに代入することにより、同じ内容の異なるビューが有効に なります。

DataSet の構成要素のほかの 2 つの重要な要素は、Xml プロパティと XmlData プロパティです。これらのプロパティは、XML を使用して DataSet の構造とコンテンツを読み取り または変更させます。Xml プロパティはスキーマとデータの両方を公開します。 XmlData プロパティはデータの読み書きだけを可能にします。

DataSet の設定

ほかのほとんどすべての ADO.NET オブジェクトと同様に、DataSet はパブリックに作成 可能なオブジェクトです。通常、独自の名前を持つか、名前を持たない DataSet を作成 します。ただし、DataSetName は DataSet の名前を取得または設定するため に使用できるプロパティです。次に、CaseSensitiveEnforceConstraints などの一部の環境属性を設定する場合があります。

前者の属性は、子 DataTable オブジェクト内で文字列を比較するときに大 文字、小文字を区別するかどうかを決定します。このプロパティは既定では False を返しま す。

EnforceConstraints は、更新操作を試みるときに確認する必要がある DataTable の Constraints を使用して任意の制約規則を設定しているかどうかを示すブール 値です。ConstraintsConstraint オブジェクトのコレクションで、各オ ブジェクトはデータの整合性を保つためにテーブルが設定する規則を定義します。

DataSet の XML の側面では、オブジェクトのコンテンツを XML にシリアル化するときに 使用する名前空間名 (Namespace プロパティ) および名前空間のプレフィックス (Prefix プロパティ) を設定することを決定できます。

作成時点では、DataSet は空で、その Tables コレクションと同様に要素を 持ちません。テーブルをコレクションに追加する基本的な方法は 2 つあります。1 つは .NET データ アダプタを使用する方法で、もう 1 つは手作業で DataTable オブジェクト を作成し、それをコレクションに追加する方法です。

データ アダプタは SQLDatasetCommand オブジェクトまたは ADODatasetCommand オブジェクトの形式をとります。 (これらのクラス名は .NET の Beta 2 で変更の対象になって いることに注意してください。)

データ アダプタは実際に行われていることを隠ぺいするメソッドを公開しますが、データ アダプタが行っていることは、ユーザーが手作業で行えることとまったく違いはありません。 DataTable オブジェクトは、まず以下のように作成します。

DataTable dt = new DataTable();

次に、オブジェクトは列と関連する属性に基づいてスキーマが指定され、最後に行が 設定されます。これに対して、DataTable の Rows コレクションを使用し、ほか の DataRow オブジェクトの後に 1 つの DataRow オブジェクトを追 加します。テーブルの準備ができると、それが Tables コレクションに挿入されます。

データ アダプタは FillDataSet メソッドの制御下でこの手順を実行します。

SQLDatasetCommand cmd = new SQLDatasetCommand(strCmd, strConn);
DataSet data = new DataSet();
cmd.FillDataSet(data, strTableName);

このようなメソッドは、指定した DataSet の指定したテーブルのデータやスキーマを更新する 目的で利用できます。FillDataSet は、アダプタのクラス コンストラクタを使用して渡されたクエリ コマンドを使って、データ ソースからデータを取得します。

そのコマンドには関連付けられた接続オブジェクトが存在する必要があります。それは、 SQLConnection オブジェクト (OLE DB データ ソースを対象にしている場合は ADOConnection オブジェクト) または単純に接続文字列のいずれかを使用して 指定できます。どちらの方法でも、必要な接続が閉じられている場合は、データを取得する ために接続が開かれ、取得後に閉じられます。FillDataSet が呼び出される前に接続が 開かれている場合は、そのまま使用され、使用後も開かれたままです。

FillDataSet は、レコードの選択可能部分のみをすべて一度に読み込ませる別の呼び 出しプロトタイプを持っています。読み込みを開始するレコードを 0 から始まる位置で指定し、 各ステップで取得する最大レコード数を指定します。この仕組みを使用して、事実上すべて のデータ ソースからのレコードの非同期読み取りを実装できます。

さらに、コマンドが複数の結果を返す場合、FillDataSet は最初の結果だけ、または一 度に読み取る最大レコード数を使用しているときは指定した部分だけが考慮に入れられる ことに注意してください。また反対にコマンドが行を返さない場合は、テーブルが DataSet に 追加されません。

DataSet にデータを設定中にエラーが発生した場合は、それまでに送信されている変更 はロール バックされない可能性があります。実際、エラーの前に追加または変更された行は そのままの状態で、キャンセルされません。DataSet にデータを設定することではなく、DataSet を最新状態に更新することが目的である場合は、主キー情報が存在していて、最初に DataSet にデータを設定するときに使用したのと同じ SQL ステートメントを使えば、行の重複を回避 できます。

リレーショナル データベースからレコードをフェッチしている場合は、通常主キーはテーブル メタ データによって推測されます。それ以外の場合は、DataTable オブジェクトの PrimaryKey プロパティを使用して設定できます。

DataColumn[] keys = new DataColumn[1];
DataTable dt = new DataTable("MyList");
keys[0] = dt.Columns["ID"];
dt.PrimaryKey = keys;

DataSet オブジェクトは、存在していて見つからない情報を復元するための 組み込みの機能を持っています。たとえば、MissingSchemaAction プロパティは、 テーブルまたは列が見つからないことが予測不能の現象を引き起こす、潜在的に一貫性 のない状況を管理する方法を示します。MissingSchemaAction に定義済みの 値を代入することにより、情報が見つからないときに単に DataSet のスキーマに情報を追 加するか、警告またはエラーを発生させるかを決定します。次のように、このプロパティに AddWithKey を設定すると、

data.MissingSchemaAction = MissingSchemaAction.AddWithKey

スキーマを完全なものにするために、必要な列とキー情報がすべて追加されます。

データ アダプタは、最終的にはデータ テーブルを作成し、テーブルにデータを設定するこ とになる専用のオブジェクトです。ユーザー自身の管理下で同じ手順を実行し、DataSet に データベース以外のレコードを設定できます。たとえば、以下のコードはディレクトリ情報を持 つテーブルを追加する方法を示しています。

DataTable dt = new DataTable();
DataColumn colName = new DataColumn();
colName.DataType = System.Type.GetType("System.String");
colName.ColumnName = "FolderName";
dt.Columns.Add(colName);
DataColumn colDesc = new DataColumn();
colDesc.DataType = System.Type.GetType("System.String");
colDesc.ColumnName = "FolderDesc";
dt.Columns.Add(colDesc);          
Directory dir = new Directory(strDir);
foreach (Directory d in dir.GetDirectories())
{
   DataRow dr = dt.NewRow();
   dr["FolderName"] = d.Name;
   dr["FolderDesc"] = "Content of " + d.Name;
   dt.Rows.Add(dr);
}
DataSet data = new DataSet();
data.Tables.Add(dt);

DataSet オブジェクトにリンクされたすべてのテーブルは、そのテーブルの 実際のソースとは無関係に、同じ API を使って操作できます。テーブルが SQL Server から作成されたか、フォルダの内容をスキャンして作成されたかによって、テーブルにリレー ションを設定したり、インデックス処理を行ったり、XML に保存したりする操作に違いが 生じることはありません。

データはすべてメモリに保持され、サーバー側の機能にはまったく頼らないで、データの 更新、並べ替え、およびフィルタ選択を行えます。多くの DBMS のトランザクション モデル に非常によく似たコミット モデルを含めて、データベースで行えるようなすべての機能がイン メモリ機能として実装されます。データ ソースに変更を保存するときのみ、実際にサーバー に戻る必要があります。これは、次のコラムで取り上げようと考えている別のカテゴリの問題 を生じます。

ダイアログ ボックス : これ以上空騒ぎ (Ado) しないで、 ADO.NET に愛着を持ってください

私はいずれ ADO.NET や ASP.NET に愛着を持つ日がくると感じていますが、 まだその日は来ていないようです。実際、次のプロジェクトでこれを使うように推奨するほ ど快適なものには感じられません。理想的には、使い始めるまで少なくとも 1 年、その 結果を世の中に出すには 2 年前後待つことになるでしょう。きっとすばらしい品質の製 品になるでしょう。何か見落としていることはありますか ?

.NET の Beta 2 の公開が近づいています。私がこの返事を書いている頃には、パ イロット プロジェクトに関係している多くの企業が既に Beta2 を使っているでしょう。あと 数週間のうちには一般に公開されるでしょう。Beta 2 を一度手に取ってみて、そのドキュ メントを注意深く読んでみてください。より快適なものと感じるでしょう。思い切って使って みることをお勧めします。

コードを書くときには、システム ドキュメントが最良の手引きになります。新しくて、まだ 未調査のプラットフォームで作業しようと考えている場合は、システム ドキュメントがさらに 大切なものになります。そのプロジェクトで収入を得るには、チームの技術力と利用でき るドキュメントに依存することにより生き残っていく必要があります。

最初の .NET プラットフォームが出荷される頃には、間違いなく優れたドキュメントを 持つことになるでしょう。Beta 2 で既に優れたドキュメントを持つようになることは、私たち が正しい方向に向かってきたことの重要な合図です。

個人的には、ASP.NET サーバー コントロール、特にカスタム コントロールについて、 最初にドキュメントを調べました。その後、関連するオブジェクトに関する (および同じメ ソッドのオーバーライドに関する) ADO.NET のドキュメントが単なる手際のよい切り貼り 結果ではないことを確認しました。最終的には、ASP.NET アプリケーションを展開する ための私の能力に自信が持てるようになりました。

.NET への愛着は、現実のプロジェクトを始めるには十分ではないかもしれません。 しかし、SDK やドキュメントが快適であると感じることは、大きな出発点になります。 これが、まさに新しい "製作" の物語を始めるために必要なことなのです。


Dino EspositoWintellect Leave-ms に勤務していて、そこで ADO.NET と ASP.NET のトレーニングとコンサルティングを行っています。彼は VB-2-The-Max Leave-ms の共同設立者で、MSDN Magazine に Cutting Edge コラムを連載しています。



Microsoft