階層行データに対するデータ操作

Priya Singh
Microsoft Developer Network

2001 年 5 月

要約: この記事では、ADO.NET を使用して単一行エンティティを返すデータ操作について説明します。これは、Microsoft .NET Framework SDK および Visual Studio.NET のベータ 1 に準拠します。

「.NET による分散アプリケーションの構築」の完全なサンプル コードは、MSDN Online Code Center からダウンロードまたは閲覧してください。

この記事で説明されている例を参照するには、直接、BDAdotNetData4.vb をご覧ください。

「.NET による分散アプリケーションの構築」のサンプル コードをダウンロードする。(50 KB)

目次

  • はじめに

  • 読み取り操作

    • ADO.NET DataReader オブジェクトを使用する
    • ADO.NET DataSet オブジェクトを使用する
  • 書込み操作

    • ADO.NET DataSetCommand オブジェクトを使用する
    • 挿入
    • 更新
    • 削除
  • 結論

  • はじめに

    この記事では、データ ソースから階層行データの読み取りと書き込みを行う技法の例を示します。この記事のコード サンプルは、SQL マネージド プロバイダを使用して、Microsoft® SQL Server™ データベースまたは Microsoft® Desktop Engine (MSDE) に接続します。その他の OLEDB 準拠データ ソースへの接続には、ADO マネージド プロバイダを使用してください。

    ADO.NET は、データ ソースから返された階層行データにアクセスするための DataReader および DataSet オブジェクトを提供します。DataReader オブジェクトは、データへの単純で高速な読み取り専用アクセスを提供することを目的として設計されています。DataReader オブジェクトは、複数の SELECT ステートメントに対応する複数の結果として、または SQL Server 2000 から XML として返された階層行にアクセスすることができます。DataReader オブジェクトでは前方専用読み取りが可能であり、アプリケーションがデータを読み取る間、データベースに接続したままです。

    これとは対照的に、DataSet オブジェクトは、非接続環境下におけるデータのメモリ内リレーショナル キャッシュを表します。複数のテーブルを含むことができ、各テーブルを個別に、または複数のテーブルを関連付けて扱うことができます。DataSet オブジェクトは、関連データのナビゲーションと変更を単純化する機能を備えています。

  • 読み取り操作

    階層行データは、しばしば、関連する行の複数のセットとして構造化されます。1 つのセットごとにデータベースへのラウンド トリップを行うよりも、1 回のトリップで複数の行セットを取得した方が効率的です。多くの場合、これは SQL 式のバッチ、または複数の SELECT ステートメントを含んだストアド プロシージャを実行することによって行われます。階層行は、FOR XML SELECT ステートメントを使用して SQL Server 2000 から XML として返される場合もあります。

    • ADO.NET DataReader オブジェクトを使用する

      この例では、2 つの SQL SELECT ステートメントのバッチが実行されて、対応する 2 つの結果セットが DataReader オブジェクトを通じて取得されます。DataReader オブジェクトでは前方専用のナビゲーションが可能なので、後続の作業のために、データは別のコンテナに転送されます。DataReader オブジェクトはデータベース接続を開いたまま保持するので、パフォーマンスの点から見て、この転送をすばやく行って、できるだけ早くデータベース接続を解放する方が有利です。分散アプリケーションの層と層の間で DataReader オブジェクトを渡すことはできません。

      DataReader オブジェクトの NextResult および Read メソッドは、結果セットの行を反復するために使用されます。


         
         Dim sqlCmd As SQLCommand
         Dim resultReader As SQLDataReader
         Try
         ' Order と OrderDetails が返されるストアド プロシージャを実行するための
         ' Command オブジェクトを準備する
         sqlCmd = New SQLCommand()
         With sqlCmd
            .CommandType = CommandType.StoredProcedure
            .CommandText = "GetOrders"
            .ActiveConnection = New _
            SQLConnection(Common.getConnectionString)
         End With
         ' 接続を開く
         sqlCmd.ActiveConnection.Open()
         ' 結果を DataReader に受けるように明示し、コマンドを実行する
         sqlCmd.Execute(resultReader)
         ' 結果セットの行を走査する
         Do
         While (resultReader.Read)
         ' ここに行に対する何らかの処理を明記
         For fld = 0 To resultReader.FieldCount - 1
         …
         Next
         …
         End While
         ' 次の結果セットへポインタを進める
         If Not (resultReader.NextResult) Then
            Exit Do
         End If
         Loop
         ' DataReader を閉じる
         resultReader.Close()
         Catch E As Exception
         ' 例外の処理
         …
         End Try
         </code>
         

完全な BDAdotNetData4.vb サンプル コードの例 1 を参照してください (ダウンロードについては、この記事の始めを参照してください)。

  • ADO.NET DataSet オブジェクトを使用する

    DataSetCommand オブジェクトは、データ ソースからデータを取得することができ、DataSet 内の DataTable オブジェクトに存在します。DataSetCommand オブジェクトは、データ ソースに対してクエリを実行するために Connection オブジェクトを必要とします。

    クエリが複数の結果セットを返した場合、DataSet オブジェクトはそれぞれ別のテーブルに格納します。テーブルを互いに関連付けることができます。

    テーブル間の関係

    DataRelation オブジェクトによって、DataSet 内の 2 つの DataTable をリレーションシップによってリンクすると、テーブルから別のテーブルへのナビゲーションが単純化されます。1 つの DataTable から、もう一方のリンクされた DataTable 内の親の行について、関連する (すなわち子の) DataRows のすべてを取得するのも単純化されます。DataRow オブジェクトのオーバーロードされた GetChildRows メソッドは、子の行を取得するために使用されます。

    リレーションシップは、一方の DataTable の行をもう一方のテーブルの行に関連付ける DataRelation オブジェクトを作成することによって確立することができます。DataSet オブジェクトは、RelationCollections オブジェクトによって含まれるリレーションシップを含みます。

    必ずしも、DataSet オブジェクト内の DataTable を関連付けなければならないわけではありません。関連付けないままにしておくこともできます。2 つの DataTable が、外部キー リレーションシップなど、何らかの種類の親子関係にある場合は、DataTable を関連付けることによって、一方の DataTable 内の親の行からもう一方の DataTable 内の子の行を取得するのが容易になります。

    次のコード例は、データベース内の Orders および OrderDetails テーブルから注文と注文詳細を取得します。DataSet オブジェクトは、それらのデータベース テーブルに対応する Orders および Details テーブルを含みます。DataSet オブジェクト内の 2 つのテーブル間に外部キー リレーションシップを定義して、互いにリンクします。列 OrderId は、両方の DataTable にあり、2 つの DataTable 間のリンクの役目を果たします。


         
         Dim sqlDSCmd as SQLDataSetCommand
         Dim hierDataSet as DataSet
         Try
         ' SQLDataSetCommand オブジェクトを生成する
         sqlDSCmd = New SQLDataSetCommand()
         ' 新しい DataSet オブジェクトを生成する
         hierDataSet = New DataSet()
         ' DataSet 内のテーブルにデータベースのテーブルをマッピングする
         sqlDSCmd.TableMappings.Add("Table", "Orders")
         sqlDSCmd.TableMappings.Add("Table", "Details")
         ' SelectCommand によって、Orders、OrderDetails を返す
         ' ストアド プロシージャを実行する
         With sqlDSCmd
         With .SelectCommand
            .CommandType = CommandType.StoredProcedure
            .CommandText = "GetOrders"
            .ActiveConnection = New SQLConnection(myConnString)
         End With
         '返されたデータによってDataSetを取得する
         .FillDataSet(hierDataSet)
         End With
         Catch
         ' 例外の処理
         …
         End Try
         </code>
         

完全な BDAdotNetData4.vb サンプル コードの例 2 を参照してください (ダウンロードについては、この記事の始めを参照してください)。

FillDataSet メソッドを呼び出す前は、DataSet オブジェクト内にテーブルはないので、SQLDataSetCommand オブジェクトは DataSet オブジェクトのためのテーブルを自動的に作成して、返されたデータを入れます。FillDataSet メソッドを実行する前にテーブルを作成した場合は、SQLDataSetCommand オブジェクトは既存のテーブルにデータを入れるだけです。


   
   ' テーブルのための主キーを設定する
   hierDataSet.Tables("Orders").PrimaryKey = _
   New DataColumn() {hierDataSet.Tables("Orders").Columns("OrderId")}
   hierDataSet.Tables("OrderDetails").PrimaryKey = _
   New DataColumn() {hierDataSet.Tables("OrderDetails").Columns("OrderDetailId")}
   ' テーブル間における外部キーの関連付けを構築する
   hierDataSet.Relations.Add(New DataRelation("Order_Detail", _
   New DataColumn() {hierDataSet.Tables("Orders").Columns("OrderId")}, _
   New DataColumn() {hierDataSet.Tables("OrderDetails").Columns("OrderId")}))

   ' Order を表示
   orderRow = hierDataSet.Tables("Orders").Rows(0)
   System.Diagnostics.Debug.WriteLine(orderRow("OrderId").ToString &amp; ", " &amp; _
   orderRow("OrderDate").ToString &amp; ", " &amp; _
   orderRow("ShipToName").ToString)
   ' Order のための子の行を取得する
   detailRows = orderRow.GetChildRows("Order_Detail")
   ' 子の行のコレクションによるなんらかの処理
   For i = 0 To detailrows.Length – 1
   detailRow = detailRows(i)
   sline = (detailRow("OrderId").ToString &amp; ", " &amp; _
   detailRow("OrderDetailId").ToString &amp; ", " &amp; _
   detailRow("ItemId").ToString &amp; ", " &amp; _
   detailRow("UnitPrice").ToString &amp; ", " &amp; _
   detailRow("Quantity").ToString)
   Next
   </code>
   
  • 書き込み操作

    2 つ以上の関連するテーブルからの複数の結果セットを含む階層データの変更をコミットするには、データの整合性を維持する必要があります。たとえば、参照整合性とは、参照するテーブル内の外部キーは、参照されるテーブル内の有効な行を常に参照しなければならないことを意味します。したがって、参照されるテーブル内の親の行が、参照するテーブル内で参照されている場合は、その行を削除することはできません。同様に、参照されるテーブル内に対応する行が存在しない場合は、参照するテーブルに挿入を行うことはできません。

    ADO.NET DataSet オブジェクトではデータベース内のデータの取得、操作、および更新が可能なので、挿入、更新、および削除のときに、2 つのテーブル間のリレーションシップが調和を保つようになっています。また、データの整合性を保ったままで、カスケード更新および削除を行うこともできます。

    • ADO.NET DataSetCommand オブジェクトを使用する

      DataSetCommand オブジェクトの Update メソッドは、DataSet オブジェクト内にキャッシュされた変更をデータ ソースにサブミットします。DataSetCommand オブジェクトは、新しい行をサブミットするには InsertCommand を、変更された行をサブミットするには UpdateCommand を、データベースから行を削除するには DeleteCommand を使用します。

      DataSetCommand オブジェクトの InsertCommandUpdateCommand、および DeleteCommand プロパティを指定した場合、Update メソッドは、DataSet オブジェクト内の挿入、更新、または削除された行のそれぞれについて、InsertUpdate、または Delete コマンドを実行します。そうでない場合は、Update メソッドは ForceGenerateUpdate というメソッドを呼び出して、InsertUpdate、および Delete コマンドを自動生成します。Update メソッドを呼び出すと、DataSetCommand オブジェクトは変更された行を調べて、その行の保留中の変更をサブミットするために、どの DataCommand を実行するかを判断します。

      望ましいのは、独自の InsertCommandDeleteCommand、または UpdateCommand を完全に指定することです。そうすれば、更新方法を明示的に制御することができ、自動生成よりもパフォーマンスが向上します。独自のコードを書いて、複数行に対する操作を 1 回のラウンド トリップで処理することによってデータ ソースへのラウンド トリップ回数を減らす場合には、この方法が特に有効です。

      DataSetCommand オブジェクトの InsertCommandUpdateCommand、および DeleteCommand プロパティについて、パラメータ化クエリまたはストアド プロシージャを指定することができます。パラメータ化クエリまたはストアド プロシージャのパラメータは、DataTable オブジェクト内の列に対応します。結果として、DataSetCommand オブジェクトは、データベース内の単一テーブルに対する更新をサポートします。したがって、データベースの更新をサブミットするときには、DataSet オブジェクト内のすべてのテーブルについて個別の DataSetCommand オブジェクトが必要です。

    • 挿入

      親テーブル内の有効な (すなわち、すでに存在する) 行に対応する場合は、子テーブルに新しい行を追加することができます。親テーブル内の無効な行を参照する行を子テーブルに追加することはできません。

      まず、新しい行を親テーブルに挿入してから、それに対応する子行だけを子テーブルに追加しなければなりません。同じ理由で、変更をデータベースと調整するときには、親テーブルに対応する DataSetCommand オブジェクトに対して Update コマンドを最初に呼び出す必要があります。

      自動生成された Insert コマンド

      自動生成された挿入には、Identity 列の主キー Id が DataSet に返されないという問題があります。これに対処するために、親の主キーを返すストアド プロシージャを使用して、子行の自動生成挿入の使用を可能にします。次のコード例では、2 つの DataSetCommand オブジェクトを作成して、単一の DataSet オブジェクト内の 2 つのテーブルにポピュレートします。DataSet オブジェクト内の 2 つのテーブル間のリレーションシップを定義して、テーブルに新しい行を挿入します。親テーブル (この例では Orders) に対応する DataSetCommand オブジェクトの Update メソッドが最初に呼び出されて、変更をデータベースにサブミットします。その後、子テーブル (この例では OrderDetails) に対応する DataSetCommand オブジェクトの Update メソッドを呼び出します。


         
         'Order テーブルのための SQLDataSetCommand オブジェクトを生成する
         Dim sqlDSCmdOrder as New SQLDataSetCommand
         'OrderDetails テーブルのための SQLDataSetCommand オブジェクトを生成する
         Dim sqlDSCmdDetail as New SQLDataSetCommand
         'DataSet オブジェクトを生成する
         Dim resultDataSet as New DataSet
         …
         'sqlDSCmdOrder オブジェクトの SelectCommand プロパティを設定する
         …
         sqlDSCmdOrder.SelectCommand.CommandText = "GetOrderHeaders"
         '返されたデータから DataSet を生成する
         sqlDSCmdOrder.FillDataSet(hierDataSet, "Orders")
         'sqlDSCmdOrder オブジェクトの SelectCommand プロパティを設定する
         …
         sqlDSCmdDetail.SelectCommand.CommandText = _
            "Select * from OrderDetails"
         '返されたデータから DataSet を生成する
         sqlDSCmdDetail.FillDataSet(hierDataSet, "Details")
         'Orders テーブルのための主キーを設定する
         …
         'Details テーブルのための主キーを設定する
         …
         'テーブル間における外部キーの関連付けを構築する
         hierDataSet.Relations.Add(New DataRelation("Order_Detail", _
         New System.Data.DataColumns() _
         {hierDataSet.Tables("Orders").Columns("OrderId")}, _
         New System.Data.DataColumns() _
         {hierDataSet.Tables("Details").Columns("OrderId")}))
         'DataSet に Orders テーブルのための新しい行を作成する
         orderRow = hierDataSet.Tables("Orders").NewRow()
         '各列(column)に値を与える
         orderRow.Item("CustomerId") = CustomerId
         orderRow.Item("OrderStatus") = OrderStatus
         orderRow.Item("OrderDate") = DateNow
         orderRow.Item("SubTotal") = SubTotal
         …
         'DataSet へ行を追加する
         hierDataSet.Tables("Orders").Rows.Add(orderRow)
         'データソースへ変更を反映させる
         sqlDSCmdOrder.Update(hierDataSet, "Orders")
         'DataSet に Details テーブルのための新しい行を作成する
         detailRow = hierDataSet.Tables("Details").NewRow()
         '各列(column)に値を与える
         'Orders テーブルにおける OrderID は、同一の値を持つ行なので
         'Orders テーブルの値を追加する
         detailRow.Item("OrderId") = orderRow.Item("OrderId")
         detailRow.Item("ItemId") = ItemId
         detailRow.Item("UnitPrice") = UnitPrice
         detailRow.Item("Quantity") = Quantity
         'DataSet に行を追加する
         hierDataSet.Tables("Details").Rows.Add(detailRow)
         sqlDSCmdOrder.Update(hierDataSet, "Details")
         </code>
         

完全な BDAdotNetData4.vb サンプル コードの例 3 を参照してください (ダウンロードについては、この記事の始めを参照してください)。

子テーブルに追加された新しい行が、親テーブルに追加された新しい行に対応する場合、データの整合性を維持するために、親テーブルの更新を最初に行う必要があります。子行は、常に親テーブル内の有効な行を参照しなければなりません。

InsertCommand プロパティを使用する

この例では、注文書ヘッダーと詳細が XML としてストアド プロシージャに渡されるので、データベースへの 1 回のラウンド トリップでトランザクションを行うことができます。

SQLDataSetCommand オブジェクトに対して Update メソッドが呼び出されるときに実行される独自の INSERT コマンドを指定するには、InsertCommand プロパティを設定します。InsertCommand プロパティは、パラメータ化クエリまたはストアド プロシージャに設定することができます。InsertCommand のパラメータは、Command オブジェクトの場合と同じように定義されます。SQL マネージド プロバイダによって、名前付きパラメータを使用することができます。

すべてのパラメータについて SourceColumn プロパティを設定する必要があります。SourceColumn は、テーブル内のどの列が値を提供するかを DataSetCommand オブジェクトに指示します。これを、実際のパラメータ名に設定します。


   
   'Select コマンドのための空の OrderID を明示する
   sqlDSCmdOrder.SelectCommand.CommandText = "GetOrderHeader @OrderId=-1"
   'sqlDSCmdOrder オブジェクトのための Insert コマンドを明示する
   With sqlDsCmdOrder.InsertCommand
   .CommandType = CommandType.StoredProcedure
   .CommandText = "InsertOrder"
   .ActiveConnection = sqlConn
   ' Insert ステートメントのパラメータを定義する
   ' 注文は、@Order を通じて XML としてストアド プロシージャへ渡される
   .Parameters.Add _
   (New SQLParameter("@Order", SQLDataType.NVarChar, 4000))
   ' Direction プロパティに値を与える
   .Parameters("@Order").Direction = ParameterDirection.Input
   ' 主キーは @OrderId を通じて返される
   .Parameters.Add _
   (New SQLParameter("@OrderId", SQLDataType.Int))
   ' Direction プロパティに値を与える
   .Parameters("@OrderId").Direction = ParameterDirection.Output
   ' SourceColumn プロパティに値を与える
   .Parameters("@OrderId").SourceColumn = "OrderId"
   End With
   '返されたデータから DataSet を生成する
   sqlDSCmdOrder.FillDataSet(hierDataSet, "Orders")
   'sqlDSCmdDetail オブジェクトのために Select コマンドを明示する
   sqlDSCmdDetail.SelectCommand.CommandText = "Select * from OrderDetails"
   sqlDSCmdDetail.FillDataSet(hierDataSet, "Details")
   'Orders テーブルのための新しい行を生成する
   orderRow = hierDataSet.Tables("Orders").NewRow()
   ' ヘッダデータを埋める
   ' テンポラリの主キー id を利用
   orderRow.Item("OrderId") = -1
   orderRow.Item("CustomerId") = 1
   orderRow.Item("OrderStatus") = 400
   …
   ' DataSet へ行を追加する
   hierDataSet.Tables("Orders").Rows.Add(orderRow)
   ' Details テーブルのために新しい行を作成する
   detailRow = hierDataSet.Tables("Details").NewRow()
   detailRow.Item("OrderId") = orderRow.Item("OrderId")
   detailRow.Item("ItemId") = 1
   detailRow.Item("UnitPrice") = 16.99
   detailRow.Item("Quantity") = 7
   ' DataSet へ行を追加する
   …
   ' @Order パラメータへ DataSet の XML 表現を適用する
   sqlDsCmdOrder.InsertCommand.Parameters("@Order").Value = hierDataSet.XmlData()
   ' データソースへ変更を反映する
   sqlDsCmdOrder.Update(hierDataSet, "Orders")
   </code>
   

完全な BDAdotNetData4.vb サンプル コードの例 4 を参照してください (ダウンロードについては、この記事の始めを参照してください)。

  • 更新

    関連するテーブル内の行は、データの整合性を妨げない限り、更新することができます。テーブルが参照整合性を維持しなければならない場合、子の行は常に親テーブル内の有効な行を参照しなければなりません。親テーブルの更新を子にカスケードして、整合性を保つことができます。

    自動生成された Update コマンド

    DataSet オブジェクト内のテーブル行を更新し、DataSetCommand オブジェクトに対して Update メソッドを呼び出すことによって、変更をデータ ソースにサブミットできます。DataSetCommand オブジェクトは、指定された Select コマンドに基づいて、Update コマンドを自動生成します。

    次のコード例では、更新操作がどのようにカスケードされるかを示すために、注文の詳細を別の注文に移動しています。


      
      'Order テーブルのための SQLDataSetCommand オブジェクトを生成する
      Dim sqlDSCmdOrder as New SQLDataSetCommand
      'OrderDetails テーブルのための SQLDataSetCommand オブジェクトを生成する
      Dim sqlDSCmdDetail as New SQLDataSetCommand
      'DataSet オブジェクトを生成する
      Dim resultDataSet as New DataSet
      …
      'SQLDataSetCommand オブジェクトの SelectCommand プロパティを設定する
      sqlDSCmdOrder.SelectCommand.CommandText = “Exec GetOrderHeader @OrderId=1”
      …
      '返されたデータから DataSetを生成する
      sqlDSCmdOrder.FillDataSet(hierDataSet, "Orders")
      'SQLDataSetCommand オブジェクトの SelectCommand プロパティを設定する
      sqlDSCmdDetail.SelectCommand.CommandText = "Exec GetORderDetails @OrderId=1”
      '返されたデータから DataSet を生成する
      sqlDSCmdDetail.FillDataSet(hierDataSet, "Details")
      'Orders テーブルのための主キーを設定
      …
      'Details テーブルのための主キーを設定
      …
      'テーブル間における外部キーの関連付けを構築する
      hierDataSet.Relations.Add(New DataRelation("Order_Detail", _
      New System.Data.DataColumns() _
      {hierDataSet.Tables("Orders").Columns("OrderId")}, _
      New System.Data.DataColumns() _
      {hierDataSet.Tables("Details").Columns("OrderId")}))
      hierDataSet.Tables("Orders").Rows(0).Item("OrderId") = NewOrderId
      'Details へ変更を反映する
      sqlDSCmdOrder.Update(hierDataSet, "Details")
      </code>
      

完全な BDAdotNetData4.vb サンプル コードの例 5 を参照してください (ダウンロードについては、この記事の始めを参照してください)。

UpdateCommand プロパティを使用する

自動生成された Update コマンドは、移動される行ごとにデータベースへのラウンド トリップを実行します。ストアド プロシージャは、1 回のラウンド トリップですべての詳細行を移動することができます。SQLDataSetCommand オブジェクトに対して Update メソッドが呼び出されたときに実行される独自の Update ステートメントを指定するには、UpdateCommand プロパティを設定します。UpdateCommand プロパティは、パラメータ化クエリまたはストアド プロシージャに設定することができます。UpdateCommand のパラメータは、Command オブジェクトの場合と同じように定義されます。


   
   …
   'Select コマンドを明示する
   …
   sqlDSCmdOrder.SelectCommand.CommandText = "Exec GetOrderHeaders @OrderId=2 "
   'UpdateCommand を明示する
   With sqlDsCmdOrder.UpdateCommand
   .CommandType = CommandType.StoredProcedure
   .CommandText = "MoveOrderDetails"
   .Parameters.Add(New SQLParameter("@FromOrderId", SQLDataType.Int))
   .Parameters("@FromOrderId").Direction = ParameterDirection.Input
   .Parameters("@FromOrderId").SourceColumn = "OrderId"
   .Parameters("@FromOrderId").SourceVersion = DataRowVersion.Original
   .Parameters.Add(New SQLParameter("@ToOrderId", SQLDataType.Int))
   .Parameters("@ToOrderId").Direction = ParameterDirection.Input
   .Parameters("@ToOrderId").SourceColumn = "OrderId"
   .Parameters("@ToOrderId").SourceVersion = DataRowVersion.Current
   .ActiveConnection = sqlConn
   End With
   …
   ' 他方へ order を移動させる
   hierDataSet.Tables("Orders").Rows(0)("OrderId") = 2
   ' 変更を反映させる
   sqlDSCmdOrder.Update(hierDataSet, "Orders")
   </code>
   

完全な BDAdotNetData4.vb サンプル コードの例 6 を参照してください (ダウンロードについては、この記事の始めを参照してください)。

SourceVersion プロパティによって、OrderId の元の値と現在の値を適切なストアド プロシージャ パラメータに渡して、移動を行うことができます。

  • 削除

    階層データの削除は、データベースの整合性を維持するような方法で行う必要があります。参照するテーブル内のすべての子行は、参照されるテーブル内の有効な行を参照しなければなりません。したがって、子 (参照する) テーブル内に 1 つ以上の対応する行がある限り、親 (参照される) テーブル内の行を削除することはできません。あるいは、対応する親行とともに子行も削除しなければなりません。

    ADO.NET DataSet オブジェクトは、カスケード削除をサポートします。カスケード削除では、参照されるテーブル内の対応する行を削除すると、参照するテーブル内の子行も削除されます。DataSet オブジェクト内の 2 つのテーブルの外部キー制約を定義して、テーブル行が変更されたときに実行されるアクションのコースを決めることができます。ForeignKeyConstraint プロパティの DeleteProperty メソッドを適切なオプションの 1 つに設定します。デフォルトでは、cascade に設定されます。

    自動生成された Delete コマンド

完全な BDAdotNetData4.vb サンプル コードの例 7 を参照してください (ダウンロードについては、この記事の始めを参照してください)。



'Order テーブルのための SQLDataSetCommand オブジェクトを生成する
Dim sqlDSCmdOrder as New SQLDataSetCommand
'OrderDetails テーブルのための SQLDataSetCommand オブジェクトを生成する
Dim sqlDSCmdDetail as New SQLDataSetCommand
'DataSet オブジェクトを生成する
Dim resultDataSet as New DataSet
…
' sqlDSCmdOrder のための Select コマンドを明示する
…
sqlDSCmdOrder.SelectCommand.CommandText = "GetOrderHeaders"
'返されたデータから DataSet を生成する
sqlDSCmdOrder.FillDataSet(hierDataSet, "Orders")
'sqlDSCmdDetail のための Select コマンドを明示する
…
sqlDSCmdDetail.SelectCommand.CommandText = "Select * from OrderDetails"
'返されたデータから DataSet を生成する
sqlDSCmdDetail.FillDataSet(hierDataSet, "Details")

外部キー リレーションシップの設定

Dim fkConstraint as ForeignKeyConstraint
'ForeignKeyConstraint オブジェクトを生成する
fkConstraint = New ForeignKeyConstraint  _
               (hierDataSet.Tables("Orders").Columns("OrderId"), _
               hierDataSet.Tables("Details").Columns("OrderId"))
'階層的な削除のために DeleteProperty をセットしなければならない
'デフォルトによって DeleteProperty は Cascade にセットされている
'他のオプションを使用したいのであれば、
'そのうちの 1 つを選択してセットする必要がある
fkConstraint.DeleteRule = Data.Rule.Cascade
'Details テーブルに制約を追加する
hierDataSet.Tables("Details").Constraints.Add(fkConstraint)
'テーブルにおける外部キーとの関連付けを構築する
hierDataSet.Relations.Add(New DataRelation("Order_Detail", _
   New System.Data.DataColumns() _
   {hierDataSet.Tables("Orders").Columns("OrderId")}, _
   New System.Data.DataColumns() _
   {hierDataSet.Tables("Details").Columns("OrderId")}))
'Orders テーブルの最終行を見つける
orderRow = hierDataSet.Tables("Orders").Rows.Item(hierDataSet.Tables_
   ("Orders").Rows.ount - 1)
orderRow.Delete()
' 変更を反映させる
sqlDSCmdDetail.Update(hierDataSet, "Details")
sqlDSCmdOrder.Update(hierDataSet, "Orders")

データの整合性を維持するために、最初に、データベース内の参照するテーブルから子行が削除されていることに注目してください。対応する子行を削除する前に、もう一方の親テーブルから行を削除しようとすると、削除は失敗します。したがって、sqlDSCmdOrder オブジェクトの更新の前に、Details テーブルの sqlDSCmdDetail オブジェクトの更新が呼び出されます。

DeleteCommand プロパティを使用する

SQLDataSetCommand オブジェクトに対して Update メソッドが呼び出されたときに実行される独自の Delete ステートメントを指定するには、DeleteCommand プロパティを設定します。DeleteCommand プロパティは、パラメータ化クエリまたはストアド プロシージャに設定することができます。DeleteCommand のパラメータは、Command オブジェクトの場合と同じように定義されます。

すべてのパラメータについて SourceColumn プロパティを設定する必要があります。SourceColumn は、テーブル内のどの列が値を提供するかを DataSetCommand オブジェクトに指示します。これを実際のパラメータ名に設定します。


   
   'sqlDSCmdOrder のための Select コマンドを明示する
   …
   sqlDSCmdOrder.SelectCommand.CommandText = "GetOrderHeaders"
   'sqlDSCmdOrder のための Delete コマンドを明示する
   …
   With sqlDSCmdOrder.DeleteCommand
   .CommandType = CommandType.StoredProcedure
   .CommandText = "DeleteOrder"
   .ActiveConnection = sqlConn
   .Parameters.Add(New SQLParameter("@OrderId", SQLDataType.Int))
   ' SourceColumn プロパティを設定する
   .Parameters("@OrderId").SourceColumn = "OrderId"
   End With
   '前述の例のように order を delete する
   'ストアド プロシージャ が details を取得するように
   'ヘッダによって呼び出される場合にのみ更新する
   sqlDSCmdDetail.Update(hierDataSet, "Orders")
   </code>
   

完全な BDAdotNetData4.vb サンプル コードの例 8 を参照してください (ダウンロードについては、この記事の始めを参照してください)。

  • 結論

読み取り専用アクセスの場合は、DataReader オブジェクトが単純で高速ですが、アプリケーションがデータを読み取る間、データ ソースに接続されたままになります。このため、アプリケーションが DataReader オブジェクトを保持する時間が長くて、競合が発生すると、スケーラビリティが低下するおそれがあります。

DataSet オブジェクトは非接続環境下のリレーショナル キャッシュを提供して、階層ナビゲーションおよび変更を単純化します。カスケード書き込みは、データベースへの変更のコミットを単純化しますが、自動生成された Insert、Update、および Delete は、特にラウンド トリップの節減とクエリ キャッシングを考えると、明示的にコード化された実装よりも非効率的になりがちです。ADO.NET のベータ版で SQL Server 2000 の XML 機能をフルに利用するためには、ラウンド トリップを節減する必要があるでしょう。