次の方法で共有


.NET Framework アプリケーションのデータベースにデータを保存し直す

DataSet クラスと関連クラスは、アプリケーションがデータベースから切断されている間にアプリケーションがメモリ内のデータを操作できるようにする、2000 年代初頭のレガシ .NET Framework テクノロジです。 このテクノロジは、ユーザーがデータを変更し、変更をデータベースに保持できるアプリに特に役立ちます。 データセットは実証済みの成功したテクノロジですが、新しい .NET アプリケーションには Entity Framework Core を使用することをお勧めします。 Entity Framework は、オブジェクト モデルとして表形式データを操作するより自然な方法を提供し、よりシンプルなプログラミング インターフェイスを備えています。

データセットは、データのメモリ内コピーです。 そのデータを変更する場合は、それらの変更をデータベースに保存することをお勧めします。 これは、次の 3 つの方法のいずれかで行います。

  • TableAdapter の Update メソッドのいずれかを呼び出す

  • TableAdapter の DBDirect メソッドのいずれかを呼び出すことによって

  • データセットにデータセット内の他のテーブルに関連するテーブルが含まれている場合に Visual Studio によって生成される TableAdapterManager で UpdateAll メソッドを呼び出す

データセット テーブルを Windows フォームまたは XAML ページ上のコントロールにデータバインドする場合、データ バインディング アーキテクチャはすべての処理を行います。

TableAdapters に慣れている場合は、次のいずれかのトピックに直接進むことができます。

トピック 説明
データベースに新しいレコードを挿入する TableAdapters オブジェクトまたは Command オブジェクトを使用して更新と挿入を実行する方法
TableAdapter を使用してデータを更新する TableAdapters を使用して更新を実行する方法
階層更新 2 つ以上の関連テーブルを含むデータセットから更新を実行する方法
コンカレンシー例外を処理する 2 人のユーザーがデータベース内の同じデータを同時に変更しようとしたときに例外を処理する方法
方法: トランザクションを使用してデータを保存する システムを使用してトランザクションにデータを保存する方法。 Transactions 名前空間と TransactionScope オブジェクト
トランザクションにデータを保存する トランザクション内のデータベースにデータを保存する方法を示す Windows フォーム アプリケーションを作成するチュートリアル
データベースにデータを保存する (複数のテーブル) レコードを編集し、複数のテーブルの変更をデータベースに保存する方法
オブジェクトからデータベースにデータを保存する TableAdapter DbDirect メソッドを使用して、データセット内にないオブジェクトからデータベースにデータを渡す方法
TableAdapter DBDirect メソッドを使用してデータを保存する TableAdapter を使用して SQL クエリをデータベースに直接送信する方法
データセットを XML として保存する データセットを XML ドキュメントに保存する方法

2 段階の更新

データ ソースの更新は 2 段階のプロセスです。 最初の手順では、データセットを新しいレコード、変更されたレコード、または削除されたレコードで更新します。 アプリケーションがそれらの変更をデータ ソースに送り返さない場合は、更新が完了します。

変更をデータベースに送り返す場合は、2 番目の手順が必要です。 データ バインド コントロールを使用していない場合は、データセットの設定に使用したのと同じ TableAdapter (またはデータ アダプター) の Update メソッドを手動で呼び出す必要があります。 ただし、異なるアダプターを使用して、データ ソース間でデータを移動したり、複数のデータ ソースを更新したりすることもできます。 データ バインディングを使用せず、関連するテーブルの変更を保存する場合は、自動生成された TableAdapterManager クラスの変数を手動でインスタンス化し、その UpdateAll メソッドを呼び出す必要があります。

データセットの更新の概念図

データセットには、行のコレクションを含むテーブルのコレクションが含まれています。 基になるデータ ソースを後で更新する場合は、行を追加または削除するときに、 DataTable.DataRowCollection プロパティのメソッドを使用する必要があります。 これらのメソッドは、データ ソースの更新に必要な変更の追跡を実行します。 Rows プロパティで RemoveAt コレクションを呼び出した場合、削除はデータベースに伝達されません。

データセットをマージする

データセットを別のデータセットとマージすることで、データセットの内容 更新できます。 これには、 ソース データセットの内容を呼び出し元のデータセット ( ターゲット データセットと呼ばれます) にコピーすることが含まれます。 データセットをマージすると、ソース データセット内の新しいレコードがターゲット データセットに追加されます。 さらに、ソース データセット内の追加の列がターゲット データセットに追加されます。 データセットのマージは、ローカル データセットがあり、別のアプリケーションから 2 つ目のデータセットを取得する場合に便利です。 また、XML Web サービスなどのコンポーネントから 2 つ目のデータセットを取得する場合や、複数のデータセットのデータを統合する必要がある場合にも便利です。

データセットをマージするときに、ターゲット データセットに既存の変更を保持するかどうかをMergeメソッドに指示するブール引数 (preserveChanges) を渡すことができます。 データセットは複数のバージョンのレコードを保持するため、複数のバージョンのレコードがマージされることに注意することが重要です。 次の表は、2 つのデータセット内のレコードがどのようにマージされるかを示しています。

DataRowVersion ターゲット データセット ソース データセット
元の画像サイズ James Wilson James C. Wilson
電流 Jim Wilson James C. Wilson

前のテーブルで Merge メソッドを preserveChanges=false targetDataset.Merge(sourceDataset) 呼び出すと、次のデータが返されます。

DataRowVersion ターゲット データセット ソース データセット
元の画像サイズ James C. Wilson James C. Wilson
電流 James C. Wilson James C. Wilson

preserveChanges = true targetDataset.Merge(sourceDataset, true)を使用して Merge メソッドを呼び出すと、次のデータが得られます。

DataRowVersion ターゲット データセット ソース データセット
元の画像サイズ James C. Wilson James C. Wilson
電流 Jim Wilson James C. Wilson

注意事項

preserveChanges = trueシナリオでは、ターゲット データセット内のレコードに対して RejectChanges メソッドが呼び出されると、ソース データセットの元のデータに戻ります。 つまり、元のデータ ソースをターゲット データセットで更新しようとすると、更新する元の行が見つからない可能性があります。 別のデータセットにデータ ソースの更新されたレコードを入力し、マージを実行してコンカレンシー違反を防ぐことで、コンカレンシー違反を防ぐことができます。 (コンカレンシー違反は、データセットの入力後に別のユーザーがデータ ソース内のレコードを変更したときに発生します)。

制約を更新する

既存のデータ行を変更するには、個々の列のデータを追加または更新します。 データセットに制約 (外部キーや null 非許容制約など) が含まれている場合は、更新時にレコードが一時的にエラー状態になる可能性があります。 つまり、1 つの列の更新が完了した後、次の列に移動する前にエラー状態になる可能性があります。

早期制約違反を防ぐために、更新制約を一時的に中断できます。 これは、次の 2 つの目的に役立ちます。

  • 1つの列の更新を完了し、別の列の更新を開始していない場合に、エラーが発生しないようにします。

  • 特定の更新イベント (検証によく使用されるイベント) が発生するのを防ぎます。

Windows フォームでは、データ グリッドに組み込まれているデータ バインディング アーキテクチャは、フォーカスが行から移動するまで制約チェックを中断し、 BeginEditEndEdit、または CancelEdit メソッドを明示的に呼び出す必要はありません。

データセットに対して Merge メソッドが呼び出されると、制約は自動的に無効になります。 マージが完了すると、有効にできないデータセットに制約がある場合は、 ConstraintException がスローされます。 この場合、 EnforceConstraints プロパティは false, に設定され、 EnforceConstraints プロパティを trueにリセットする前に、すべての制約違反を解決する必要があります。

更新が完了したら、制約チェックを再び有効にできます。制約チェックは、更新イベントを再度有効にして発生させることもできます。

イベントの中断の詳細については、「データセットの 入力中に制約を無効にする」を参照してください。

データセットの更新エラー

データセット内のレコードを更新すると、エラーが発生する可能性があります。 たとえば、誤った型のデータを誤って列に書き込んだり、長すぎるデータや、他の整合性の問題があるデータを書き込んだりすることがあります。 または、更新イベントの任意の段階でカスタム エラーが発生する可能性がある、アプリケーション固有の検証チェックがある場合もあります。 詳細については、「 データセット内のデータを検証する」を参照してください。

変更に関する情報を保持する

データセットの変更に関する情報は、変更されたことを示す行にフラグを設定する (RowState)、レコードの複数のコピーを保持する (DataRowVersion) という 2 つの方法で保持されます。 この情報を使用することで、プロセスはデータセット内で何が変更されたかを判断し、適切な更新をデータ ソースに送信できます。

RowState プロパティ

DataRow オブジェクトの RowState プロパティは、特定のデータ行の状態に関する情報を提供する値です。

次の表では、 DataRowState 列挙体の使用可能な値について詳しく説明します。

DataRowState 列挙定数の値 説明
Added 行が項目として DataRowCollectionに追加されました。 (この状態の行は、最後の AcceptChanges メソッドが呼び出されたときに存在しなかったため、対応する元のバージョンを持っていません)。
Deleted 行は、DataRow オブジェクトのDeleteを使用して削除されました。
Detached 行は作成されましたが、 DataRowCollectionの一部ではありません。 DataRow オブジェクトは、作成された直後、コレクションに追加される前、およびコレクションから削除された後に、この状態になります。
Modified 行の列の値が何らかの方法で変更されました。
Unchanged AcceptChangesが最後に呼び出されてから、行は変更されていません。

DataRowVersion 列挙型

データセットは複数のバージョンのレコードを保持します。 DataRowVersion フィールドは、DataRow オブジェクトの Item[] プロパティまたは GetChildRows メソッドを使用して、DataRowで見つかった値を取得するときに使用されます。

次の表では、 DataRowVersion 列挙体の使用可能な値について詳しく説明します。

DataRowVersion 列挙定数の値 説明
Current レコードの現在のバージョンには、最後に AcceptChanges が呼び出されてからレコードに対して実行されたすべての変更が含まれています。 行が削除されている場合、現在のバージョンはありません。
Default データセット スキーマまたはデータ ソースで定義されているレコードの既定値。
Original レコードの元のバージョンは、データセットで最後に変更がコミットされた時点のレコードのコピーです。 実際には、これは通常、データ ソースから読み取られるレコードのバージョンです。
Proposed 更新中に一時的に使用できるレコードの提案されたバージョン。つまり、 BeginEdit メソッドを呼び出してから EndEdit メソッドを呼び出した時点の間です。 通常は、 RowChangingなどのイベントのハンドラー内のレコードの提案されたバージョンにアクセスします。 CancelEdit メソッドを呼び出すと、変更が元に戻され、提案されたバージョンのデータ行が削除されます。

元のバージョンと現在のバージョンは、更新情報がデータ ソースに送信されるときに便利です。 通常、更新プログラムがデータ ソースに送信されると、データベースの新しい情報はレコードの現在のバージョンに格納されます。 元のバージョンからの情報は、更新するレコードを検索するために使用されます。

たとえば、レコードの主キーが変更された場合、変更を更新するためにデータ ソース内の正しいレコードを見つける方法が必要です。 元のバージョンが存在しない場合、レコードはデータ ソースに追加される可能性が最も高く、余分な不要なレコードだけでなく、不正確で古い 1 つのレコードになります。 2 つのバージョンは、コンカレンシー制御でも使用されます。 元のバージョンをデータ ソース内のレコードと比較して、データセットに読み込まれてからレコードが変更されたかどうかを判断できます。

提案されたバージョンは、データセットへの変更を実際にコミットする前に検証を実行する必要がある場合に便利です。

レコードが変更された場合でも、その行の元のバージョンまたは現在のバージョンが常に存在するとは限りません。 テーブルに新しい行を挿入すると、元のバージョンはなく、現在のバージョンのみが存在します。 同様に、テーブルの Delete メソッドを呼び出して行を削除すると、元のバージョンは存在しますが、現在のバージョンはありません。

データ行の HasVersion メソッドに対してクエリを実行することで、レコードの特定のバージョンが存在するかどうかをテストできます。 列の値を要求するときに、省略可能な引数として DataRowVersion 列挙値を渡すことで、いずれかのバージョンのレコードにアクセスできます。

変更されたレコードを取得する

データセット内のすべてのレコードを更新しないのが一般的な方法です。 たとえば、ユーザーは、多数のレコードを表示する Windows フォーム DataGridView コントロールを使用している可能性があります。 ただし、ユーザーは少数のレコードのみを更新し、1 つを削除して、新しいレコードを挿入することができます。 データセットとデータ テーブルは、変更された行のみを返すメソッド (GetChanges) を提供します。

変更されたレコードのサブセットは、データ テーブル (GetChanges) またはデータセット (GetChanges) 自体のGetChangesメソッドを使用して作成できます。 データ テーブルのメソッドを呼び出すと、変更されたレコードのみを含むテーブルのコピーが返されます。 同様に、データセットでメソッドを呼び出すと、変更されたレコードのみが含まれる新しいデータセットが取得されます。

GetChanges それ自体は、変更されたすべてのレコードを返します。 これに対し、必要な DataRowState をパラメーターとして GetChanges メソッドに渡すことで、新しく追加されたレコード、削除対象としてマークされるレコード、デタッチされたレコード、または変更されたレコードなど、変更されたレコードのサブセットを指定できます。

変更されたレコードのサブセットを取得することは、レコードを別のコンポーネントに送信して処理する場合に便利です。 データセット全体を送信する代わりに、コンポーネントに必要なレコードのみを取得することで、他のコンポーネントと通信するオーバーヘッドを削減できます。

データセットの変更をコミットする

データセットで変更が行われると、変更された行の RowState プロパティが設定されます。 レコードの元のバージョンと現在のバージョンは、 RowVersion プロパティによって確立、管理、および使用できるようになります。 これらの変更された行のプロパティに格納されているメタデータは、データ ソースに正しい更新を送信するために必要です。

変更にデータ ソースの現在の状態が反映されている場合は、この情報を保持する必要はなくなります。 通常、データセットとそのソースが同期されている場合は 2 回あります。

  • ソースからデータを読み取るときなど、データセットに情報を読み込んだ直後。

  • データセットからデータソースに変更を送信した後に(データベースに変更を送信するために必要な変更情報を失わないよう、以前には送信しないでください)。

AcceptChanges メソッドを呼び出すことで、保留中の変更をデータセットにコミットできます。 通常、 AcceptChanges は次の時刻に呼び出されます。

  • データセットを読み込んだ後。 TableAdapter の Fill メソッドを呼び出してデータセットを読み込む場合、アダプターは自動的に変更をコミットします。 ただし、別のデータセットをマージしてデータセットを読み込む場合は、変更を手動でコミットする必要があります。

    アダプターの AcceptChangesDuringFill プロパティを false に設定することで、Fill メソッドを呼び出すときに、アダプターが変更を自動的にコミットしないようにすることができます。 falseに設定されている場合、塗りつぶし中に挿入される各行のRowStateAddedに設定されます。

  • データセットの変更を XML Web サービスなどの別のプロセスに送信した後。

    注意事項

    この方法で変更をコミットすると、変更情報が消去されます。 データセットで行われた変更をアプリケーションで認識する必要がある操作の実行が完了するまで、変更をコミットしないでください。

この方法では、次の処理が実行されます。

  • レコードの Current バージョンを Original バージョンに書き込み、元のバージョンを上書きします。

  • RowState プロパティが Deleted に設定されている行を削除します。

  • レコードの RowState プロパティを Unchangedに設定します。

AcceptChangesメソッドは、3 つのレベルで使用できます。 DataRow オブジェクトで呼び出して、その行の変更のみをコミットできます。 DataTable オブジェクトで呼び出して、テーブル内のすべての行をコミットすることもできます。 最後に、 DataSet オブジェクトで呼び出して、データセットのすべてのテーブルのすべてのレコードのすべての保留中の変更をコミットできます。

次の表では、メソッドが呼び出されるオブジェクトに基づいてコミットされる変更について説明します。

メソッド 結果
System.Data.DataRow.AcceptChanges 変更は特定の行でのみコミットされます。
System.Data.DataTable.AcceptChanges 変更は、特定のテーブルのすべての行でコミットされます。
System.Data.DataSet.AcceptChanges 変更は、データセットのすべてのテーブルのすべての行に対してコミットされます。

TableAdapter の Fill メソッドを呼び出してデータセットを読み込む場合、変更を明示的に受け入れる必要はありません。 既定では、 Fill メソッドは、データ テーブルの設定が完了した後、 AcceptChanges メソッドを呼び出します。

関連するメソッド RejectChangesOriginal バージョンを Current バージョンのレコードにコピーすることで、変更の影響を元に戻します。 また、各レコードの RowStateUnchangedに戻します。

データの検証

アプリケーション内のデータが渡されるプロセスの要件を満たしていることを確認するには、多くの場合、検証を追加する必要があります。 これには、フォーム内のユーザーのエントリが正しいことを確認したり、別のアプリケーションによってアプリケーションに送信されたデータを検証したり、コンポーネント内で計算された情報がデータ ソースとアプリケーションの要件の制約に含まれているかどうかを確認したりする場合があります。

データは、いくつかの方法で検証できます。

  • ビジネス 層で、データを検証するコードをアプリケーションに追加します。 データセットは、これを行うことができる 1 つの場所です。 データセットには、列と行の値が変化するにつれて変更を検証する機能など、バックエンド検証の利点がいくつかあります。 詳細については、「 データセット内のデータを検証する」を参照してください。

  • プレゼンテーション レイヤーで、フォームに検証を追加します。 詳細については、「 Windows フォームでのユーザー入力の検証」を参照してください。

  • データ バックエンドでは、データ ソース (データベースなど) にデータを送信し、データの受け入れまたは拒否を許可します。 データを検証し、エラー情報を提供するための高度な機能を備えたデータベースを使用している場合は、データの取得元に関係なくデータを検証できるため、実用的なアプローチになります。 ただし、この方法では、アプリケーション固有の検証要件に対応できない場合があります。 さらに、データ ソースでデータを検証すると、アプリケーションがバックエンドによって発生する検証エラーの解決を容易にする方法に応じて、データ ソースへのラウンド トリップが多数発生する可能性があります。

    Von Bedeutung

    Textに設定されているCommandType プロパティでデータ コマンドを使用する場合は、クライアントから送信された情報をデータベースに渡す前に注意深く確認してください。 悪意のあるユーザーは、未承認のアクセスを取得したり、データベースに損害を与えたりするために、変更または追加の SQL ステートメントを送信 (挿入) しようとする可能性があります。 ユーザー入力をデータベースに転送する前に、常に情報が有効であることを確認してください。 可能であれば、常にパラメーター化されたクエリまたはストアド プロシージャを使用することをお勧めします。

データ ソースに更新を送信する

データセットで変更が行われた後、変更をデータ ソースに送信できます。 最も一般的には、TableAdapter (またはデータ アダプター) の Update メソッドを呼び出すことによってこれを行います。 このメソッドは、データ テーブル内の各レコードをループ処理し、必要な更新の種類 (更新、挿入、または削除) を決定してから、適切なコマンドを実行します。

更新方法の図として、アプリケーションで 1 つのデータ テーブルを含むデータセットを使用しているとします。 アプリケーションは、データベースから 2 つの行を取得します。 取得後、メモリ内データ テーブルは次のようになります。

(RowState)     CustomerID   Name             Status
(Unchanged)    c200         Robert Lyon      Good
(Unchanged)    c400         Nancy Buchanan    Pending

アプリケーションによって、Nancy Buchanan の状態が "Preferred" に変更されます。この変更の結果、その行の RowState プロパティの値が Unchanged から Modifiedに変わります。 最初の行の RowState プロパティの値は Unchangedのままです。 データ テーブルは次のようになります。

(RowState)     CustomerID   Name             Status
(Unchanged)    c200         Robert Lyon      Good
(Modified)     c400         Nancy Buchanan    Preferred

これで、アプリケーションで Update メソッドが呼び出され、データセットがデータベースに送信されます。 メソッドは各行を順番に検査します。 最初の行の場合、このメソッドは SQL ステートメントをデータベースに送信しません。これは、その行が最初にデータベースからフェッチされてから変更されていないためです。

ただし、2 行目の場合、 Update メソッドは自動的に正しいデータ コマンドを呼び出し、データベースに送信します。 SQL ステートメントの具体的な構文は、基になるデータ ストアでサポートされている SQL の方言によって異なります。 ただし、送信される SQL ステートメントの次の一般的な特徴は注目に値します。

  • 送信される SQL ステートメントは、 UPDATE ステートメントです。 アダプターは、RowState プロパティの値がModifiedされているため、UPDATE ステートメントを使用することを認識します。

  • 転送される SQL ステートメントには、UPDATE ステートメントのターゲットがCustomerID = 'c400'行であることを示すWHERE句が含まれています。 CustomerIDはターゲット テーブルの主キーであるため、SELECT ステートメントのこの部分はターゲット行を他のすべての行と区別します。 WHERE句の情報は、行の識別に必要な値が変更された場合に、レコードの元のバージョン (DataRowVersion.Original) から派生します。

  • 送信される SQL ステートメントには、変更された列の新しい値を設定する SET 句が含まれています。

    TableAdapter の UpdateCommand プロパティがストアド プロシージャの名前に設定されている場合、アダプターは SQL ステートメントを構築しません。 代わりに、渡された適切なパラメーターを使用してストアド プロシージャを呼び出します。

パラメーターを渡す

通常、パラメーターを使用して、データベースで更新されるレコードの値を渡します。 TableAdapter の Update メソッドで UPDATE ステートメントを実行する場合は、パラメーター値を入力する必要があります。 適切なデータ コマンド (この場合は TableAdapter のUpdateCommand オブジェクト) のParameters コレクションからこれらの値を取得します。

Visual Studio ツールを使用してデータ アダプターを生成した場合、 UpdateCommand オブジェクトには、ステートメント内の各パラメーター プレースホルダーに対応するパラメーターのコレクションが含まれます。

各パラメーターの System.Data.SqlClient.SqlParameter.SourceColumn プロパティは、データ テーブル内の列を指します。 たとえば、au_idパラメーターと Original_au_id パラメーターのSourceColumn プロパティは、データ テーブル内の任意の列に作成者 ID が含まれている値に設定されます。アダプターの Update メソッドを実行すると、更新中のレコードから作成者 ID 列が読み取られ、値がステートメントに入力されます。

UPDATEステートメントでは、新しい値 (レコードに書き込まれる値) と古い値の両方を指定する必要があります (レコードをデータベースに配置できるようにします)。 そのため、値ごとに 2 つのパラメーターがあります。1 つは SET 句用、別のパラメーターは WHERE 句用です。 どちらのパラメーターも更新中のレコードからデータを読み取りますが、パラメーターの SourceVersion プロパティに基づいて異なるバージョンの列値を取得します。 SET句のパラメーターは現在のバージョンを取得し、WHERE句のパラメーターは元のバージョンを取得します。

Parameters コレクションの値をコードで自分で設定することもできます。これは通常、データ アダプターのRowChanging イベントのイベント ハンドラーで行います。