ADO.NET 2.0 の新しい DataSet 機能

Jackie Goldstein
Renaissance Computer Systems

November 2004
日本語版最終更新日 2005 年 1 月 6 日

適用対象:
   Microsoft ADO.NET 2.0
   Visual Basic プログラミング言語

概要: .NET Framework の DataSet クラス、および DataSet と密接に関連するクラスについて、ADO.NET 2.0 で新たに導入される機能を紹介します。 具体的な変更点としては、機能とパフォーマンスの両面における DataSet、DataTable、および DataView の各クラスの強化などが挙げられます。

この記事に関連する DataSetSamples.exe のサンプルコード のダウンロード

目次

はじめに
パフォーマンスの生データ
DataTable の独立性向上
ストリームからキャッシュへ、キャッシュからストリームへ
まとめ

はじめに

近くリリースされる ADO.NET の次期バージョン ADO.NET 2.0 では、各種の .NET Framework クラスやアプリケーション開発シナリオに関係する機能の導入と強化が多数予定されています。 この記事では、非接続モードの ADO.NET Framework クラスの中核となる DataSetDataTableDataView などの DataSet 関連クラスの変更と機能拡張について説明します。

この記事は、ADO.NET 2.0 の DataSet とその関連クラスを扱う 2 つの記事の前編に当たります。 ここでは主に .NET Framework のクラスを取り上げます。 後編の記事では、Visual Studio 2005 開発環境でこれらのクラスや関連クラスを使って開発する方法を中心に説明します。 Visual Studio 2005 には、アプリケーションのデータ中心設計の柔軟性と生産性を極限まで高めるデザイナとツールがいくつか用意されています。 各記事の焦点にこのような違いがあるため、それぞれの印象は異なったものになります。 この記事では主に新機能の概要について解説とサンプル コードを交えながら紹介し、次の記事では開発プロセスに話の中心を移して実動アプリケーションの開発方法を見ていきます。

先に述べたとおり、この記事で扱うのは ADO.NET 2.0 の新機能のごく一部です。 他のいくつかの機能については、「ADO.NET 2.0 の機能マトリックス」で概要を紹介しています。 その中にあるトピックの一部については、以下の記事に詳細な説明があります。

特に明記しない限り、この記事の内容は Visual Studio 2005 の Beta 1 リリースに基づいています。 サンプル コードでは、SQL Server 2000 にサンプル データベースとして添付されている Northwind データベースを使用しています。

パフォーマンスの生データ

ソフトウェア開発者は常にパフォーマンスに気を配っています。 パフォーマンスを気にするあまり、わずかな実行時間を削るために、本質的には重要でない部分のコードを苦労して書き換えることもありますが、その話は別の記事に譲ることにします。 ADO.NET 1.x の DataSet のパフォーマンスを巡っては開発者から懸念の声が上がっていましたが、特に大量のデータを含む DataSet については、その懸念は正当と言わざるを得ません。 大量のデータを含む DataSet の処理が低速になるケースは 2 つあります。 まず初めにパフォーマンスの低下を感じるのは、行数の多い DataSet (実際には DataTable) を読み込むときです。 DataTable 内の行数が増えると、その行数にほぼ比例して、新しい行の読み込み時間が長くなります。 また、大量のデータを含む DataSet のシリアル化やリモート処理を行う際にもパフォーマンスに影響が出ます。 DataSet に関しては、特にアプリケーション階層間の受け渡しの際にシリアル化の方法が自動認識される点が大きな特徴となっていますが、詳しく見てみると、このシリアル化は非常に冗長でメモリとネットワークの帯域幅を大量に消費することがわかります。 ADO.NET 2.0 では、このようなパフォーマンス上のボトルネックがどちらも解消されています。

新しいインデックス エンジン

DataTable のインデックス エンジンは ADO.NET 2.0 で完全に書き直され、大きなデータセットの処理能力が大幅に向上しています。 その結果、基本的な挿入、更新、削除が高速になり、Fill 操作や Merge 操作も高速になっています。 ベンチマークやパフォーマンス向上の定量化は常にアプリケーションに依存するため話は単純ではありませんが、この変更により、100 万行の DataTable の読み込みでは明らかに桁違いのパフォーマンス向上が見られます。 ただし、この言葉をそのまま鵜呑みにせず、以下の簡単な例を使ってご自身で確認してください。 以下のコードを、Windows フォーム上のボタンのクリック イベント ハンドラとして追加してください。


    Private Sub LoadButton_Click(ByVal sender As System.Object, 
ByVal e As System.EventArgs) Handles LoadButton.Click
        Dim ds As New DataSet
        Dim time1 As New Date

        Dim i As Integer
        Dim dr As DataRow
        ds.Tables.Add("BigTable")
        ds.Tables(0).Columns.Add("ID", Type.GetType("System.Int32"))
        ds.Tables(0).Columns("ID").Unique = True
        ds.Tables(0).Columns.Add("Value", Type.GetType("System.Int32"))

        ' 状態ラベルを表示します。
        WaitLabel.Visible = True
        Me.Cursor = Cursors.WaitCursor
        Me.Refresh()

        ' 開始時刻を取得します。
        time1 = DateTime.Now()

        ' DataTable に 100 万行を読み込みます。
        '
        ' このコードを ADO.NET 1.1 でコンパイルして実行すると、
        ' コーヒーを沸かして飲めるだけの時間がかかります。
        Dim rand As New Random
        Dim value As Integer

        For i = 1 To 1000000
            Try
                value = rand.Next

                dr = ds.Tables(0).NewRow()

                dr("ID") = value
                dr("Value") = value

                ds.Tables(0).Rows.Add(dr)

            Catch ex As Exception
                ' ID の列を一意に指定しているため、
                ' 重複する値があると、
                ' 例外が発生します。
            End Try
        Next

        ' カーソルとラベルをリセットします。
        WaitLabel.Visible = False
        Me.Cursor = Me.DefaultCursor

        ' 経過時間を秒単位で表示します。
        MessageBox.Show("Elapsed Time: " & _
DateDiff(DateInterval.Second, time1, DateTime.Now))

        ' テーブル内の行数を確認してください。
        ' 行数はループ回数よりもたいてい少なくなります。
        ' これは、同じ乱数があると、
        ' テーブルに追加できないためです。
        MessageBox.Show("count = " & ds.Tables(0).Rows.Count)

    End Sub
                

筆者の環境で ADO.NET 1.1 と Visual Studio 2003 を使ってこのコードを実行したところ、実行時間は約 30 分でした。 ADO.NET 2.0 と Visual Studio 2005 を使った場合、実行時間は約 40 ~ 50 秒になりました。 行の数を 50 万行にしたところ、バージョン 1.1 では約 45 秒かかったのに対し、バージョン 2.0 での所要時間は約 20 秒でした。 環境によって結果は異なりますが、要点は明らかだと思います。

実はこの例は非常に単純な例で、一意の列に対する 1 つのインデックスしか含まれていません。 しかし、たとえば DataViewUniqueKeyForeignKey を追加することで DataTable のインデックス数が増えると、パフォーマンスの差ははるかに大きくなります。

メモ   このサンプル コードでは、単にループ カウンタを ID として使用するのではなく、ID の値を乱数ジェネレータで生成していますが、これは現実のシナリオにより近づけるためです。 実際のアプリケーションでは、DataTable の要素の挿入、更新、および削除を行う際、各要素にシーケンシャルにアクセスすることはほとんどありません。 各操作では、まず一意のキーが示す行を探す必要があります。 行の挿入や削除を行う場合には、テーブルのインデックスを更新する必要があります。 単にシーケンシャルなキー値を持つ 100 万の行を空のテーブルに読み込んだだけでは、結果は非常に高速になり、誤解を招くことになります。

バイナリ シリアル化オプション

大量のデータが入った DataTable を読み込む際のパフォーマンスは、既存の ADO.NET 1.x コードを一切変更しなくても大幅に向上します。 一方、DataSet をシリアル化する際のパフォーマンスを実際に向上させるにはもう少し労力が必要で、新しい RemotingFormat プロパティを設定するためのコードを 1 行だけ追加する必要があります。

ADO.NET 1.x では、バイナリ フォーマッタを使用した場合でも、DataSet は XML としてシリアル化されます。 ADO.NET 2.0 ではこれに加え、RemotingFormat プロパティを SerializationFormat.XML (既定値) ではなく SerializationFormat.Binary に設定することで、実際にバイナリ形式でシリアル化するよう指定できるようになっています。 この 2 つの異なるオプションの出力結果を見てみましょう。

ADO.NET チームが常に心がけている下位互換性の維持のため、XML シリアル化の既定値では ADO.NET 1.x と同じ動作が実行されるようになっています。 このシリアル化の結果は、以下のコードを実行することで確認できます。


    Private Sub XMLButton_Click(ByVal sender As System.Object, 
ByVal e As System.EventArgs) Handles XMLButton.Click
        Dim ds As New DataSet
        Dim da As New SqlDataAdapter("select * from [order details]", _
 GetConnectionString())
        da.Fill(ds)

        Dim bf As New BinaryFormatter
        Dim fs As New FileStream("..\xml.txt", FileMode.CreateNew)
        
        bf.Serialize(fs, ds)
    End Sub
                

Private Function GetConnectionString() As String
        ' コード中に接続文字列がハードコーディングされるのを避けるため、
        ' アプリケーション設定を使用します。

        Return MySettings.Value.NorthwindConnection

End Function
                

このコードでは BinaryFormatter クラスを明示的に使用しているにもかかわらず、図 1 に示す xml.txt ファイルの出力が明らかに XML になっている点に注意してください。 また、この場合のファイル サイズは 388 KB です。

ms971494.datasetenhance_01(ja-jp,MSDN.10).gif


ここで次の行を追加し、シリアル化の形式をバイナリに変更します。


ds.RemotingFormat = SerializationFormat.Binary

                

FileStream コンストラクタ内のファイル名を変更して、データを別のファイルに保存します。コードは以下のようになります。


    Private Sub BinaryButton_Click(ByVal sender As System.Object, 
ByVal e As System.EventArgs) Handles BinaryButton.Click
        Dim ds As New DataSet
        Dim da As New SqlDataAdapter("select * from [order details]", _
GetConnectionString())
        da.Fill(ds)

        Dim bf As New BinaryFormatter
        Dim fs As New FileStream("..\binary.txt", FileMode.CreateNew)

        ds.RemotingFormat = SerializationFormat.Binary

        bf.Serialize(fs, ds)
    End Sub
                

binary.txt ファイルの出力を図 2 に示します。 今度は実際にバイナリ データになっていて、人間の目ではほとんど判読できません。 また、ファイル サイズは 59 KB しかありません。転送が必要なデータの量や、その処理に必要な CPU、メモリ、および帯域幅のリソースがここでも桁違いに減ります。 ただし、この方法でパフォーマンスが向上するのはリモート処理を使用する場合であり、Web サービスを使用する場合には該当しません。Web サービスではその性質上、XML を受け渡す必要があるためです。 したがって、この機能拡張を利用できるのは通信の両端が .NET ベースの場合のみであり、非 .NET プラットフォームと通信する場合には利用できません。

ms971494.datasetenhance_02(ja-jp,MSDN.10).gif


DataSet のシリアル化処理の詳細については、「Binary Serialization of DataSets」を参照してください。

DataTable の独立性向上

ADO.NET 1.x とそのオブジェクト モデルによる非接続モードのデータ アクセスについて検討する場合、中心となるオブジェクトは DataSet でした。 DataTableDataRelationDataRow といったオブジェクトももちろん存在しましたが、DataSet とその周辺に関心が集まるのが常でした。 .NET の開発者の大多数は DataTable 自体の利便性を認識し、DataSet にカプセル化されていない状態でも活用していましたが、作成した DataTableDataSet に取り込まないと目的の処理を実行できないようなシナリオがいくつかあったのも事実です。 DataTable に対して XML データの読み書き (読み込みと保存) を行う場合がその最たる例であり、骨の折れる作業が必要になるケースも少なくありませんでした。 ADO.NET 1.x では、XML の読み書きに必要なメソッドが DataSet にしか用意されていないため、XML を読み書きするだけでも DataTableDataSet に追加する必要があったのです。

ADO.NET 2.0 の目標の 1 つが、ADO.NET 1.x のときよりもはるかに機能的で便利なスタンドアロンの DataTable を開発することでした。 その結果、DataSet と同様に、DataTable でも XML 用の基本メソッドがサポートされるようになりました。 これには以下のメソッドが含まれます。

  • ReadXML
  • ReadXMLSchema
  • WriteXML
  • WriteXMLSchema

DataTable は個別にシリアル化が可能で、Web サービスとリモート処理の両方のシナリオで使用できます。 スタンドアロンの DataTable では、Merge メソッドに加え、ADO.NET 2.0 で DataSet に追加された 新機能もサポートされます。

  • RemotingFormat プロパティ (前述)
  • Load メソッド (後述)
  • GetDataReader メソッド (後述)
メモ   XML に関連して、ADO.NET 2.0 で XML のサポートが格段に向上している点は注目に値します。Microsoft はこれを "XML 忠実度" の向上と呼んでいます。 具体的な内容としては、SQL Server 2005 の XML データ型のサポート、XSD スキーマのサポート拡張、および XSD スキーマ推論エンジンの強化が挙げられます。これに加え、問題になることが多かった 2 つの制約が解消され、 (i) DataSet クラスと DataTable クラスで複数のインライン スキーマを扱えるようになり、(ii) DataSet で名前空間が完全にサポートされ、名前が同じで名前空間が違う (つまり、非修飾名が同じで修飾名が違う) 複数の DataTable を 1 つの DataSet に格納できるようになりました。 また、名前も名前空間も同じ子テーブルが複数のリレーションシップに含まれる場合に、その子テーブルを複数の親テーブルの入れ子にできるようになっています。

ストリームからキャッシュへ、キャッシュからストリームへ

ADO.NET 2.0 における DataSet クラスと DataTable クラスの機能拡張の中でもう 1 つ重要なのは、DataReader (DataTable へのデータの読み込み) を使い、DataTable の内容の上に DataReader を表示する仕組みが用意されている点です。

DataReader 形式で保持しているデータや DataReader 形式で受け取ったデータを、キャッシュされた DataTable の形式で保持したい場合があります。 新しい Load メソッドを使えば、既存の DataReader を読み込んで DataTable にその内容を格納できます。

また、キャッシュ形式 (DataTable) で保持しているデータやキャッシュ形式で受け取ったデータに、DataReader 型のインターフェイスでアクセスする必要が生じる場合もあります。 新しい GetTableReader メソッドを使えば、既存の DataTable を読み込んで、DataReader のインターフェイスとセマンティクスを使ってアクセスできます。

以降のセクションでは、これらの新しいメソッドについて説明します。

Load メソッド – 基本的な使い方

Load メソッドは、ADO.NET 2.0 で DataSetDataTable に追加された新しいメソッドで、 DataTableDataReader オブジェクトの内容を読み込む機能を備えています。 実際には、DataReader に複数の結果セットが含まれていれば、一度に複数のテーブルを読み込むこともできます。

Load メソッドの基本的な使い方は、次のようにとても簡単です。


MyDataTable.Load (MyDataReader)

                  
                

より詳しい使い方を以下のサンプル コードに示します。


 Private Sub LoadButton_Click(ByVal sender As System.Object, 
ByVal e As System.EventArgs) Handles LoadButton.Click
        Try
            Using connection As New SqlConnection(GetConnectionString())
                Using command As New SqlCommand("SELECT * from customers", connection)
                    connection.Open()
                    Using dr As SqlDataReader = command.ExecuteReader()
                        ' テーブルに DataReader からデータを読み込みます。
                        Dim dt As New DataTable
                        dt.Load(dr, LoadOption.OverwriteRow)

                        ' データを表示します。
                        DataGridView1.DataSource = dt
                    End Using
                End Using
            End Using

        Catch ex As SqlException
            MessageBox.Show(ex.Message)
        Catch ex As InvalidOperationException
            MessageBox.Show(ex.Message)
        Catch ex As Exception
            ' このエラーを呼び出し元に
            ' 返すことができます。
            MessageBox.Show(ex.Message)
        End Try
    End Sub

                

このコードでは、接続オブジェクトとコマンド オブジェクトを初期化した後、ExecuteReader メソッドを実行してデータベースからデータを取得しています。 クエリの結果は DataReader として返され、さらに DataTableLoad メソッドに渡されて、返されたデータがテーブルに格納されます。 DataTable に格納されたデータは、DataGridView にバインドして表示できます。 省略可能な LoadOption パラメータの OverwriteRow 読み込みオプションの重要性については、次のセクションで説明します。

Load メソッド – データを読み込む理由

DataSetDataTableDataAdapter と組み合わせて実行する処理が、データ ソースから読み込んだデータを DataSet に格納し、変更を加えた後にいずれかの時点でデータ ソースに戻すというだけのことならば、事は順調に運びます。 複雑になるのは、オプティミスティック同時実行制御を利用した処理で同時実行違反が検出された場合 (変更しようとしている行を他のだれかが既に変更している場合) です。 この場合は、競合を解消するために、通常は DataSet をデータ ソースと再同期させ、該当する行の元の値をデータベースの現在の値に一致させる必要があります。 この処理は、新しい値を持つ DataTable を元のテーブルにマージすることで実行できます (ADO.NET 1.x では、Merge メソッドは DataSet にしかありません)。


OriginalTable.Merge(NewTable, True)

                

同じ主キーを持つ行との照合により、新しいテーブルのレコードが元のテーブルのレコードとマージされます。 ここで重要になるのが、2 つ目のパラメータである PreserveChanges です。 これは、マージ操作で各行の元の値だけを更新し、行の現在の値は変更しないことを指定するパラメータです。 これによって元の値をデータ ソースの現在の値と一致させることができるため、引き続き DataAdapter.Update を実行し、データ ソースに変更 (現在の値) を反映できるようになります。 PreserveChanges を既定値の false のままにすると、マージにより元の DataTable を構成する各行の元の値と現在の値の両方が上書きされ、すべての変更が失われます。

ただし、データ ソースのデータを更新したい場合もあります。新しい値がプログラム的に変更したものではなく、別のデータベースや XML ソースから取得したものである場合などが考えられます。 このシナリオでは、DataTable を構成する各行の現在の値を更新し、元の値はそのままにしておく必要があります。 ADO.NET 1.x では、これを簡単に行う方法はありません。 このような理由から、ADO.NET 2.0 の Load メソッドにはパラメータ LoadOption を渡せるようになっており、新しい入力行と DataTable 内に既にある同じ (主キーが同じ) 行をどのように結合させるかを指定できます。

LoadOption により、データを読み込む際の意図 (同期またはアグリゲーション) と、その意図に基づいて新しい行と既存の行をどのようにマージするかを明示的に指定できます。 図 3 にいくつかのシナリオを示します。

ms971494.datasetenhance_03(ja-jp,MSDN.10).gif


各用語の意味は以下のとおりです。

  • プライマリ データ ソースDataTableDataSet は、1 つのプライマリ データ ソースに対してのみ同期または更新を行います。 プライマリ データ ソースとの同期のため、変更が追跡されます。
  • セカンダリ データ ソースDataTableDataSet は、1 つ以上のセカンダリ データ ソースから増分データを受け取ります。 セカンダリ データ ソースは、同期を目的とした変更追跡の対象にはなりません。

図 3 に示す 3 つのケースについて以下に概要を説明します。

  • ケース 1プライマリ データ ソースに基づいて DataTable を初期化。 空の DataTable (元の値と現在の値) をプライマリ データ ソースから取得した値で初期化し、そのデータの変更が完了したら、プライマリ データ ソースに変更を反映します。
  • ケース 2変更を保持したままプライマリ データ ソースと再同期。 変更された DataTable を活かすため、変更 (現在の値) は保持したまま、その内容 (元の値のみ) をプライマリ データ ソースと再同期します。
  • ケース 31 つ以上のセカンダリ データ ソースからの増分データを集約。 1 つ以上のセカンダリ データ ソースから変更 (現在の値) を受け取り、プライマリ データ ソースに反映します。

LoadOption 列挙体には 3 つの値があり、それぞれ上の 3 つのシナリオに対応しています。

  • OverwriteRow–行の現在のバージョンと元のバージョンを、入力行の値で更新します。
  • PreserveCurrentValues (既定) –行の元のバージョンを入力行の値で更新します。
  • UpdateCurrentValues–行の現在のバージョンを入力行の値で更新します。
メモ   これらの名前は、Beta 1 の後で変更される見込みです。

Load メソッドのセマンティクスを表 1 にまとめます。 入力行と既存の行の主キー値が一致すれば、その行は既存の DataRowState を使って処理されます。一致しない場合は、'Not Present' のセクションが使われます (テーブルの最後の行)。

表 1. Load メソッドのセマンティクスの要約

既存の DataRow の状態UpdateCurrentValues OverwriteRow PreserveCurrentValues (既定)
Added現在の値 = <入力>

元の値 = - --

状態 = <Added>

現在の値 = <入力>

元の値 = <入力>

状態 = <Unchanged>

現在の値 = <既存>

元の値 = <入力>

状態 = <Modified>

Modified現在の値 = <入力>

元の値 = <既存>

状態 = <Modified>

現在の値 = <入力>

元の値 = <入力>

状態 = <Unchanged>

現在の値 = <既存>

元の値 = <入力>

状態 = <Modified>

Deleted

(削除を元に戻すと)

現在の値 = <入力>

元の値 = <既存>

状態 = <Modified>

(削除を元に戻すと)

現在の値 = <入力>

元の値 = <入力>

状態 = <Unchanged>

現在の値 = <既存>

元の値 = <入力>

状態 = <Deleted>

Unchanged現在の値 = <入力>

元の値 = <既存>

新しい値が既存の値と同じ場合

状態 = <Unchanged>

新しい値と既存の値が異なる場合

状態 = <Modified>

現在の値 = <入力>

元の値 = <入力>

状態 = <Unchanged>

現在の値 = <入力>

元の値 = <入力>

状態 = <Unchanged>

Not Present現在の値 = <入力>

元の値 = - --

状態 = <Added>

現在の値 = <入力>

元の値 = <入力>

状態 = <Unchanged>

現在の値 = <入力>

元の値 = <入力>

状態 = <Unchanged>


表 1 にまとめた動作を説明するため、簡単な例を示します。

既存の DataRow と入力行には列が 2 つあり、名前が一致しているものとします。 最初の列が主キーで、次の列には数値が入っています。 以下の表は、データ行の 2 列目の内容を示します。

表 2 は、Load を呼び出す前の 4 つの状態における行の内容を示します。 入力行の第 2 列の値は 3 です。 表 3 は、読み込み後の内容を示します。

表 2. 読み込み前の行の状態

既存の行の状態バージョンAddedModifiedDeletedUnchanged
 22-4
 -444

入力行

入力行
3

表 3. 読み込み後の行の状態

 UpdateCurrentValues OverwriteRow PreserveCurrentValues

Added

現在の値 = <3>

元の値 = - --

状態 = <Added>

現在の値 = <3>

元の値 = <3>

状態 = <Unchanged>

現在の値 = <2>

元の値 = <3>

状態 = <Changed>

Modified

現在の値 = <3>

元の値 = <4>

状態 = <Modified>

現在の値 = <3>

元の値 = <3>

状態 = <Unchanged>

現在の値 = <2>

元の値 = <3>

状態 = <Changed>

Deleted

現在の値 = <3>

元の値 = <4>

状態 = <Modified>

現在の値 = <3>

元の値 = <3>

状態 = <Unchanged>

現在の値 = <2>

元の値 = <3>

状態 = <Deleted>

Unchanged現在の値 = <3>

元の値 = <4>

状態 = <Modified>

現在の値 = <3>

元の値 = <3>

状態 = <Unchanged>

現在の値 = <3>

元の値 = <3>

状態 = <Unchanged>

Not Present現在の値 = <3>

元の値 = - --

状態 = <Added>

現在の値 = <3>

元の値 = <3>

状態 = <Unchanged>

現在の値 = <3>

元の値 = <3>

状態 = <Unchanged>


メモ   この考え方は、ADO.NET 1.x でも既にありました。 DataAdapter の Fill メソッドの既定動作では、データを DataTable に読み込む際に、すべての行が Unchanged とマークされます (この動作は、AcceptChangesOnFill プロパティに False を設定することで変更できます)。 しかし、ReadXML を使ってデータを DataSet に読み込む際には、行は Added とマークされます。 これはお客様からのフィードバックに基づいて実装された動作で、新しいデータを XML ソースから DataSet に読み込み、対応する DataAdapter を使ってプライマリ データ ソースを更新できるようにすることを目的としています。 ReadXML からの読み込み時に行が Unchanged とマークされていると、DataAdapter.Update で変更が検出されず、データ ソースに対してコマンドが実行されません。

同様の機能を実現するため、DataAdapterFillLoadOptions プロパティが追加され、Fill メソッドの動作を (既定では) 現在と同じに保ちつつ、ここで説明した Load メソッドと同じセマンティクスや動作を実現できるようになっています。

ADO.NET 1.x で開発者から絶えず要望のあったもう 1 つの機能 (存在しない機能) は、DataRow の状態を手動で変更する機能です。 Load メソッドに用意されているオプションによりほとんどのシナリオがカバーされるとはいえ、行の状態をより細かく制御したい場合があります。たとえば、各行の状態を個別に変更しなくてはならない場合が考えられます。 そのために ADO.NET 2.0 では、DataRow クラスに新しく SetAddedSetModified の 2 つのメソッドが追加されました。 疑問に先回りして説明しておくと、状態を DeletedUnchanged に設定する場合には、バージョン 1.x に既に存在する Delete メソッドや AcceptChanges/RejectChanges メソッドで目的の処理を実行できます。

GetTableReader メソッド

GetTableReader メソッドは、ADO.NET 2.0 で DataSetDataTable に追加された新しいメソッドです。 このメソッドは、DataTable の内容を DataTableReader オブジェクト (DBDataReader から派生) として返します。 複数のテーブルを含む DataSet に対して実行した場合、DataReader には複数の結果セットが含まれます。


GetTableReader メソッドの使い方は、次のようにとても簡単です。

Dim dtr As DataTableReader = ds.Tables(0).GetDataReader

                

DataTableReader は、これまで使ってきた SqlDataReaderOleDbDataReader などの他のデータ リーダーと非常によく似た動作をします。 DataTableReader が違っている点は、接続中のデータベース接続からストリームとして読み込むのではなく、接続されていない DataTable の各行に対して繰り返し処理を行うことです。

DataTableReader により、スマートで安定した繰り返し処理を行うことができます。 キャッシュされたデータは DataTableReader がアクティブな間に変更することができ、繰り返しの際に 1 つ以上の行が削除または挿入された場合でも、リーダーが自動的に位置を正しく維持します。

DataTableGetDataReader を呼び出した場合、作成される DataTableReader には、作成元の DataTable と同じデータが入った結果セットが 1 つ含まれます。 この結果セットには、各 DataRow に対応する列の現在の値だけが含まれ、削除マークが付けられた行はスキップされます。 複数のテーブルが含まれる DataSetGetDataReader を呼び出した場合、作成される DataTableReader には複数の結果セットが含まれます。 結果セットの順序は、DataSet オブジェクトの DataTableCollection にある DataTable オブジェクトと同じになります。

GetDataReader メソッドは、ここまでに説明した用途だけでなく、ある DataTable から別の DataTable にすばやくデータをコピーする目的にも大いに役立ちます。


Dim dt2 as new DataTable
dt2.Load(ds.Tables(0).GetDataReader)

                

DataView.ToTable メソッド

新しいメソッドの中でもう 1 つ注目に値するのが、(既存データの新しい DataTable キャッシュを返すという点で) 前述のメソッドにいくらか関係する DataView クラスの ToTable メソッドです。 念のために説明しておくと、DataView は DataTable 内の行の論理ビューを提供するクラスです。 このビューでは、行や行の状態に基づいてフィルタをかけたり並べ替えたりすることができます。 しかし ADO.NET 1.1 では、DataView 自身は行のコピーを持っておらず、フィルタや並べ替えのパラメータの指定に応じて基になっている DataTable の行にアクセスしているだけなので、ビューの行を保存したり受け渡したりする簡単な手段がありません。 DataViewToTable メソッドを実行すると、現在のビューに表示されている行のデータを格納した実際の DataTable オブジェクトが返されます。

オーバーロードされた ToTable メソッドでは、作成するテーブルに含める列のリストを指定することができます。 生成されたテーブルには、指定した列が指定した順序で含まれます (元のテーブルやビューとは順序が違うこともあります)。 このようにビューの列数を制限する機能が ADO.NET 1.x には用意されておらず、多くの .NET プログラマが不満を感じていました。 ADO.NET 2.0 ではこれが解消され、作成するテーブルの名前や、行をすべて含めるか特定の行だけを含めるかも指定できるようになっています。

ToTable メソッドの使い方を示すサンプル コードを以下に示します。


    Private Sub ToTableButton_Click(ByVal sender As System.Object, 
ByVal e As System.EventArgs) Handles ToTableButton.Click
        ' 2 つ目のグリッドに表示する列を 2 つだけにします。
        Dim columns As String() = {"CustomerID", "ContactName"}

        Dim dt As DataTable = _
ds.Tables("customers").DefaultView.ToTable( _
"SmallCustomers", False, columns)

        DataGridView2.DataSource = dt
    End Sub

                

DataSet ds 内の "customers" テーブルの内容が 1 つ目のグリッドに表示されていると仮定すると、このルーチンにより、DefaultView に表示されている行 (フィルタ パラメータで指定) のみを要素とする DataTable が新しく作成され、表示されます。 新しいテーブルの行には、元の DataTableDataView の列のうち 2 つだけが含まれます。 例を図 4 に示します。

ms971494.datasetenhance_04(ja-jp,MSDN.10).gif


まとめ

ADO.Net 2.0 バージョンの DataSet (および DataTable) では、多数の新機能が導入され、既存機能が拡張されます。 この記事で説明した主な機能としては、新しいインデックス エンジンとバイナリ シリアル化形式オプションによる大幅なパフォーマンス向上、スタンドアロンの DataTable で使用できる多彩な機能、キャッシュされたデータをストリームとして表示するための機構 (DataReader)、 DataTable キャッシュへのストリーム データの読み込みがあります。 ADO.NET 2.0 では、現実のシナリオに合わせて DataTable 内の各行の状態をより細かく制御できるようになっています。

謝辞 この記事の作成にあたり、Microsoft の Kawarjit S. Bedi 氏、Pablo Castro 氏、Alan Griver 氏、Steve Lasker 氏、Paul Yuknewicz 氏にご協力いただきました。

Jackie Goldstein 氏は、Microsoft 製のツールや技術のコンサルティング、トレーニング、および開発を専門とする Renaissance Computer Systems Non-MS link 社の社長です。 Microsoft Regional Director 、Israel VB User Group の創設者としての顔も持ち、TechEd、VSLive!、Developer Days、Microsoft PDC などの国際的な開発者向けイベントにも主要な講演者として参加しています。 著書に『Database Access with Visual Basic .NET』 (Addison-Wesley, ISBN 0-67232-3435) があり、INETA Speakers Bureau の一員にもなっています。 2003 年 12 月に、Microsoft から .NET Software Legend に指名されました。


Page view tracker