データ保存の概要
更新 : 2007 年 11 月
データ保存は、アプリケーションで変更されたデータを元のデータ ストア (通常は SQL Server などのリレーショナル データベース) に戻して保持するプロセスです。
データセットは実際にはデータのキャッシュ (インメモリ コピー) であるため、元のデータ ソースに情報を書き込むプロセスはデータセットのデータを変更するのとは別のプロセスです。データセット内の更新されたデータをデータベースに送り返すには、TableAdapter の Update メソッドのいずれかを呼び出すか、TableAdapter の DBDirect メソッドのいずれかを呼び出します。
データセットへの変更をデータベースに戻す方法の詳細については、「方法 : TableAdapter を使用してデータを更新する」および「方法 : データセットの変更をデータベースに保存する」を参照してください。
Visual Studio 2008 には、関連テーブルにデータを保存する際の保存の実行に役立つ新しい TableAdapterManager コンポーネントが用意されています。このコンポーネントにより、データベースで定義された外部キー制約に基づく適切な順序で保存されるようになります。詳細については、「階層更新の概要」を参照してください。
データセット内でデータを変更する方法については、「アプリケーションでのデータ編集」を参照してください。
2 段階の更新
データセットを介してデータ ソースを更新するには、2 段階のプロセスがあります。第 1 段階のプロセスは、新しい情報 (新しいレコード、変更されたレコード、または削除されたレコード) によるデータセットの更新です。アプリケーションがデータセットだけを扱う (たとえば、更新したデータセットは次の処理のために別のアプリケーションに送信される) 場合は、第 1 段階で更新処理を完了します。
メモ : |
---|
Windows フォームでは、データ バインディング アーキテクチャが、データ バインド コントロールからデータセットへの変更の送信を処理します。独自のコードでデータセットを明示的に更新する必要はありません。詳細については、「Windows フォームでのデータ バインディング」を参照してください。 |
データ ソース (データベースなど) を更新する場合は、第 2 段階のプロセスでデータセットの変更内容を元のデータ ソースに送ります。第 1 段階のデータセット更新プロセスでは基になるデータ ソースに変更内容が書き込まれないので、第 2 段階の更新プロセスを明示的に実行する必要があります。データ ソース更新プロセスを明示的に実行するには、通常、データセットにレコードを読み込むのに使用したのと同じ TableAdapter (またはデータ アダプタ) の Update メソッドを呼び出します。ただし、別のアダプタを使用して 1 つのデータ ソースから別のデータ ソースにデータを移動したり、複数のデータ ソースを更新したりすることもできます。
2 段階の更新プロセスと、正常な更新における DataRowVersion の役割
構造的にデータセットではデータをコレクションの集合として利用できます。データセットにはテーブルのコレクションが含まれます。テーブルには行のコレクションが含まれます。テーブルは DataSet オブジェクトのコレクションとして公開され、レコードは DataTable オブジェクトの Rows コレクションで利用できます。基本コレクション メソッドを使って単にコレクションを操作するだけでデータセットのデータを変更できますが、基になるデータ ソースを更新する場合は、データセットの変更専用にデザインされたメソッドを使う必要があります。
たとえば、データ テーブルからレコードを削除するのにテーブルの Rows コレクションの RemoveAt メソッドを呼び出すと、レコードはデータセットから物理的に削除されます。データセットをデータの構造化ストアとしてだけ使用し、変更情報を別のアプリケーションに転送する予定がない場合は、この方法でコレクションを操作してデータセットを更新しても問題ありません。
ただし、変更内容をデータ ソースまたは別のアプリケーションに送信する場合は、それぞれの更新に関して変更情報 (メタデータ) を保持しておく必要があります。後で変更内容をデータ ソースまたはアプリケーションに送信するときに、適切なレコードを探して更新するのに必要な情報をプロセスに与えるためです。たとえば、データセット内のレコードを削除する場合、削除したレコードに関する情報をデータセット内に保持しておく必要があります。このようにすると、TableAdapter の DeleteCommand が呼び出されたとき、データ ソースの元のレコードを特定する十分な履歴情報があるので、そのレコードを削除できます。詳細については、後の「変更に関する情報の保持」を参照してください。
データセットのマージ
データセットの内容はマージにより更新できます。あるデータセット (ソースデータセットと呼ぶ) の内容を呼び出し元のデータセット (ターゲット データセットと呼ぶ) にコピーするのがマージです。データセットをマージすると、ソース データセットの新しいレコードはターゲット データセットに追加されます。また、ソース データセットのその他の列もターゲット データセットに追加されます。データセットのマージは、ローカル データセットを持っていて別のアプリケーションまたは XML Web サービスのようなコンポーネントから 2 つ目のデータセットを取得する場合に特に役立ちます。また、複数のデータセットのデータを統合する必要がある場合にも役立ちます。
データセットをマージするときにオプションのブール型の引数 (preserveChanges) を渡し、ターゲット データセットの既存の変更を保持するかどうかを Merge メソッドに指定することもできます。データセットはレコードの複数のバージョンを保持しているため、レコードの複数のバージョンがマージされるということに留意することは重要です。マージする 2 つのデータセットのレコードを次の表に示します。
DataRowVersion |
ターゲットのデータセット |
元のデータセット |
---|---|---|
Original |
James Wilson |
James C. Wilson |
処理後 |
Jim Wilson |
James C. Wilson |
preserveChanges=false targetDataset.Merge(sourceDataset) である場合にこの表について Merge メソッドを呼び出すと、結果は次のようになります。
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 メソッドが呼び出されると、そのレコードは "ソース" データセットの元のデータに戻ります。これは、ターゲット データセットによって元のデータ ソースを更新しようとする場合に、更新する元の行を見つけることができない場合があることを意味しています。ただし、データ ソースの更新されているレコードを別のデータセットに格納してからマージを行うことで、同時実行違反を回避できます (同時実行違反は、データセットにレコードが格納された後で別のユーザーがデータ ソース内のレコードを変更すると発生します)。詳細については、「ADO.NET における同時実行制御」を参照してください。 |
更新の制約
既存のデータ行を変更するには、各列のデータを追加または更新します。データセットに制約 (外部キーの制約や null 非許容の制約など) が含まれている場合は、レコードを更新するとき (1 つの列の更新が終了した後、次の列の更新を始める前) にレコードが一時的にエラー状態になることがあります。
早期に制約違反を防ぐために、更新制約を一時的に中断できます。これには 2 つの目的があります。
1 つの列を更新した後、別の列を更新する前にエラーがスローされるのを防ぐ。
特定の更新イベント (検証によく使われるイベント) の発生を中断する。
更新が完了した後で制約チェックを再び有効にできます。これにより更新イベントも再び有効になり、更新イベントが発生します。
メモ : |
---|
Windows フォームでは、データ グリッドに組み込まれたデータ バインディング アーキテクチャによりフォーカスが行の外に移るまで制約チェックを中断できます。BeginEdit メソッド、EndEdit メソッド、または CancelEdit メソッドを明示的に呼び出す必要はありません。 |
データセットで Merge メソッドが呼び出されると、制約は自動的に無効になります。マージが完了したときに、有効にできない制約がデータセットにあると、ConstraintException がスローされます。この場合、EnforceConstraints プロパティが false に設定されるので、すべての制約違反を解決してから EnforceConstraints プロパティを true に設定し直す必要があります。
更新が完了した後で制約チェックを再び有効にできます。これにより更新イベントも再び有効になり、更新イベントが発生します。
イベントの中断の詳細については、「方法 : データセットの読み込み中に制約をオフにする」を参照してください。
データセットの更新エラー
データセットのレコードを更新するときにエラーが発生する場合があります。たとえば、データ型が不適切な列、データが長すぎる列、その他の整合性問題がある列にデータを誤って書き込んだ場合などです。また、更新イベントのいずれかの段階においてカスタム エラーを引き起こす可能性がある、アプリケーション固有の有効性の確認が実行される場合があります。詳細については、「データの妥当性検査の概要」を参照してください。
変更に関する情報の保持
データセットの変更に関する情報を保持するには、行が変更されているかどうかを示すフラグを設定する方法 (RowState)、レコードの複数のコピーを保持する方法 (DataRowVersion) の 2 とおりがあります。変更に関する情報を使用することにより、プロセスはデータセットの変更内容を確認し、適切な更新内容をデータ ソースに送信できます。
RowState プロパティ
DataRow オブジェクトの RowState プロパティは、データの特定の行のステータスに関する情報を提供する値です。
DataRowState 列挙定数に使用できる値の詳細を次の表に示します。
DataRowState 列挙定数の値 |
説明 |
---|---|
行は項目として DataRowCollection に追加されました。この状態にある行に対応する元のバージョンはありません。最後の AcceptChanges メソッドが呼び出されたときにこの行は存在していなかったからです。 |
|
行は作成されましたが、DataRowCollection の一部ではありません。DataRow オブジェクトがこの状態になるのは、作成直後でコレクションに追加される前、またはコレクションから削除された場合です。 |
|
行内の列の値はなんらかの方法で変更されました。 |
|
行は AcceptChanges が最後に呼び出されてから変更されていません。 |
DataRowVersion 列挙定数
データセットはレコードの複数のバージョンを保持します。DataRow オブジェクトの DataRowVersion 列挙定数を使用すると、特定のバージョンの DataRow オブジェクトを取得できます。
DataRowVersion 列挙定数に使用できる値の詳細を次の表に示します。
DataRowVersion 列挙定数の値 |
説明 |
---|---|
レコードの現在のバージョンには、AcceptChanges が最後に呼び出された後のレコードへの変更がすべて含まれています。行が削除されている場合、現在のバージョンは存在しません。 |
|
データセット スキーマまたはデータ ソースにより定義されたレコードの既定値です。 |
|
レコードの元のバージョンは、データセットで最後の変更がコミットされたときのレコードのコピーです。つまり、通常はデータ ソースから読み込まれたときのレコードのバージョンです。 |
|
更新の実行中、つまり BeginEdit メソッドの呼び出しと EndEdit メソッドの呼び出しの間に一時的に利用できる、レコードの提案されたバージョンです。通常は RowChanging などのイベントのハンドラで、レコードの提案されたバージョンにアクセスします。CancelEdit メソッドを呼び出すと、変更は無効になり、データ行の提案されたバージョンは削除されます。 |
元のバージョンおよび現在のバージョンは、更新情報をデータ ソースに送信する場合に役立ちます。通常、更新情報をデータ ソースに送信すると、データベースの新しい情報がレコードの現在のバージョンに含まれます。元のバージョンの情報は、更新するレコードを見つけるために使用されます。たとえば、レコードの主キーが変更された場合に備え、変更を更新するためにデータ ソースの適切なレコードを探す方法を用意しておく必要があります。元のバージョンがなかった場合、レコードはデータ ソースに追加される可能性が高く、結果として不要なレコードが作成されるだけでなく、不正確な古いレコードが 1 つ作成されることになります。2 つのバージョンは同時実行制御にも使用できます。元のバージョンをデータ ソースのレコードと比較し、レコードがデータセットに最後に読み込まれた後で変更されているかどうかを確認します。詳細については、「ADO.NET における同時実行制御」を参照してください。
提案されたバージョンは、実際に変更をデータセットにコミットする前に検証が必要な場合に役立ちます。
レコードが変更されていても、その行の元のバージョンまたは現在のバージョンが必ず存在するわけではありません。新しい行をテーブルに挿入した場合、元のバージョンは存在せず、現在のバージョンがあるだけです。同様に、テーブルの Delete メソッドを呼び出すことにより行を削除した場合、元のバージョンはありますが現在のバージョンはありません。
データ行の HasVersion プロパティを照会することにより、レコードの特定のバージョンが存在するかどうかを確認するテストを行うことができます。列の値を要求するときに DataRowVersion 列挙値をオプションの引数として渡すことにより、レコードのいずれかのバージョンにアクセスできます。
変更されたレコードの取得
一般に、データセットの各レコードをすべて更新することはありません。たとえば、多数のレコードを表示する Windows フォームの DataGridView コントロールをユーザーが使用しているとします。このとき、ユーザーが一部のレコードだけを更新し、レコードを 1 つ削除し、新しいレコードを 1 つ挿入したとします。そのような場合のために、データセットおよびデータ テーブルには、変更された行だけを返すためのメソッド (GetChanges) が用意されています。
データ テーブルの GetChanges メソッド (GetChanges) またはデータセットの GetChanges メソッド (GetChanges) を使って、変更されたレコードのサブセットを作成できます。データ テーブルのメソッドを呼び出すと、変更されたレコードだけを含むテーブルのコピーが返されます。同様に、データセットのメソッドを呼び出すと、変更されたレコードだけを含む新しいデータセットを取得できます。GetChanges を単独で呼び出すと、変更されたすべてのレコードが返されます。これに対し、必要な DataRowState をパラメータとして GetChanges メソッドに渡すと、変更されたレコードのうち、新しく追加されたレコード、削除とマークされたレコード、分離されたレコード、変更されたレコード、のいずれか必要なサブセットを指定できます。
変更されたレコードのサブセットの取得は、レコードを処理するために別のコンポーネントに送信する場合に特に役立ちます。データセット全体を送信する代わりに、コンポーネントが必要としているレコードだけを取得することにより、ほかのコンポーネントとの通信によるオーバーヘッドを小さくできます。詳細については、「方法 : 変更された行を取得する」を参照してください。
データセットの変更のコミット
データセットを変更すると、変更された行の RowState プロパティが設定されます。RowVersion プロパティによって、レコードの元のバージョンおよび現在のバージョンが確立されて保持され、利用可能になります。これらのプロパティには変更内容を表すメタデータが格納され、このメタデータは適切な更新内容をデータ ソースに送信するのに必要です。
変更内容がデータ ソースの現在の状態を反映している場合は、この情報を保持する必要はなくなります。通常、データセットとそのソースの同期は以下の 2 とおりの場合に保持されます。
ソースからデータを読み込んだときなど、情報をデータセットに読み込んだ直後。
変更内容をデータセットからデータ ソースに送信した後。データベースに変更内容を送信するのに必要な変更情報が失われてしまうため、送信前ではありません。
保留中の変更は、AcceptChanges メソッドを呼び出してデータセットにコミットできます。通常、AcceptChanges はアプリケーションにおいて次の場合に呼び出されます。
データセットを読み込んだ後。TableAdapter の Fill メソッドを呼び出すことによりデータセットを読み込んだ場合、TableAdapter により変更が自動的にコミットされます。ただし、別のデータセットをマージすることによりデータセットを読み込む場合は、変更を手動でコミットする必要があります。
メモ : Fill メソッドが呼び出されたときに TableAdapter が変更を自動的にコミットしないようにするには、TableAdapter の AcceptChangesDuringFill プロパティを false に設定します。false に設定した場合、読み込み中に挿入された各行の RowState は Added に設定されます。
データセットの変更内容を別のプロセス (Web サービスなど) に送信した後。
注意 : この方法で変更をコミットすると、変更情報はすべて削除されます。データセットへの変更内容に依存するアプリケーションの操作が終了するまでは、変更をコミットしないでください。
この方法で実行できる処理は次のとおりです。
AcceptChanges メソッドは、3 つのレベルで利用可能です。このメソッドを DataRow オブジェクトで呼び出すと、その行の変更だけがコミットされます。また、DataTable オブジェクトで呼び出すとテーブル内のすべての行がコミットされ、DataSet オブジェクトで呼び出すとデータセットの全テーブルの全レコードの保留中の変更がすべてコミットされます。
メソッドが呼び出されたオブジェクトに基づいてコミットされる変更を次の表に示します。
メソッド |
結果 |
---|---|
変更は特定の行にだけコミットされます。 |
|
変更は特定のテーブルのすべての行にコミットされます。 |
|
変更はデータセットのすべてのテーブルのすべての行にコミットされます。 |
メモ : |
---|
TableAdapter の Fill メソッドを呼び出すことによってデータセットを読み込む場合は、変更を明示的に受け入れる必要はありません。Fill メソッドは、データ テーブルへの読み込みが終了すると既定で AcceptChanges メソッドを呼び出します。 |
関連メソッド RejectChanges は、レコードの Original バージョンを Current バージョンにコピーし、各レコードの RowState を Unchanged に設定し直すことで、変更による影響を元に戻します。
データの検証
アプリケーションのデータが、渡される対象プロセスの要件を満たしているかどうかを検査するために、検証を追加することが必要な場合もあります。この検証には、フォームへのユーザーの入力が適切かどうかの確認、別のアプリケーションから送られたデータの検証、またはコンポーネント内で計算された情報がデータ ソースおよびアプリケーションの制約を満たしているかどうかの確認が含まれます。
データを検証するには次の方法があります。
ビジネス層で、データを検証するコードをアプリケーションに追加する。データセットでこれを行うことができます。データセットでは、列および行の値が変更されるたびに変更を検証する機能など、バック エンド検証を活用できます。詳細については、「データの妥当性検査の概要」を参照してください。
プレゼンテーション層で、検証をフォームに追加する。詳細については、「Windows フォームでのユーザー入力の検証」を参照してください。
データのバック エンドで、データをデータベースなどのデータ ソースに送信し、データの受け入れまたは拒否を行うことができるようにする。データの検証やエラー情報を提供する洗練された機能を備えたデータベースを使用している場合、実用的なアプローチになります。データのソースが何であるかにかかわらずデータを検証できるからです。ただし、アプリケーション固有の検証要件には適応できない可能性があります。また、データ ソースでデータを検証した場合に、バック エンドによって発生する検証エラーを解決する方法によってはデータへのラウンド トリップが多数発生する場合があります。
セキュリティに関するメモ : CommandType プロパティを Text に設定したデータ コマンドを使用するときは、クライアントから送信された情報をデータベースに渡す前に、その情報を十分にチェックしてください。悪意のあるユーザーが、承認なしでデータベースにアクセスしたり、データベースを破壊したりする目的で、変更した SQL ステートメントや追加の 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 プロパティにストアド プロシージャの名前が設定されている場合、TableAdapter は SQL ステートメントを作成しません。その代わりに、適切なパラメータを渡してストアド プロシージャを呼び出します。
パラメータの引き渡し
データベース内で更新するレコードの値は、通常、パラメータを使って渡されます。TableAdapter の Update メソッドは、UPDATE ステートメントを実行するときにパラメータ値を設定する必要があります。この値は、該当するデータ コマンドの Parameters コレクションから取得します。この場合は TableAdapter の UpdateCommand オブジェクトです。
Visual Studio ツールを使ってデータ アダプタを生成した場合は、ステートメントの各パラメータ プレースホルダに対応するパラメータのコレクションが UpdateCommand オブジェクトに含まれます。
各パラメータの SqlParameter.SourceColumn プロパティは、データ テーブル内の列を指しています。たとえば、au_id パラメータおよび Original_au_id パラメータの SourceColumn プロパティには、データ テーブルの author id を含む列が設定されます。アダプタの Update メソッドを実行すると、更新されるレコードから author id 列が読み取られ、ステートメントに値が設定されます。
UPDATE ステートメントにおいては、レコードに書き込まれる新しい値と、更新されるレコードをデータベースで見つけるために必要な古い値を両方とも指定する必要があります。したがって、それぞれの値に、SET 句のパラメータと WHERE 句の 2 つのパラメータがあります。両方のパラメータは更新されるレコードからデータを読み込みますが、各パラメータの SqlParameter.SourceVersion プロパティに基づいて、列ごとのバージョンを取得します。SET 句のパラメータは現在のバージョンを取得し、WHERE 句のパラメータは元のバージョンを取得します。
メモ : |
---|
Parameters コレクションの値はコードで設定することもできます。通常はデータ アダプタの RowChanging イベントのイベント ハンドラで設定します。詳細については、「データ アダプタ コマンドのパラメータ」を参照してください。 |
関連テーブルの更新
データセットに複数のテーブルが含まれている場合は、各データ アダプタの Update メソッドを個別に呼び出してそれぞれのテーブルを更新する必要があります。テーブルに親子関係がある場合は、特定の順序で更新内容をデータベースに送信する必要があります。通常、親レコードおよび関連する子レコード (たとえば、新しい顧客レコードと 1 つ以上の関連する注文レコード) の両方をデータセットに追加したような場合です。データベース自体に関係整合性の規則を適用する場合は、親レコードが作成される前に新しい子レコードをデータベースに送信するとエラーが発生します。
これに対して、データセットの関連レコードを削除する場合は、一般に逆の順序、つまり先に子テーブル、次に親テーブルという順序で更新内容を送信する必要があります。この順序に従わないとデータベースでエラーが発生する可能性が高くなります。参照整合性の規則により、関連する子レコードが存在している間は親レコードを削除できないからです。
関連テーブルの更新に関する一般的な規則に基づき、次の順序に従います。
子テーブル : レコードを削除する。
親テーブル : レコードを挿入、更新、および削除する。
子テーブル : レコードを挿入および更新する。
詳細については、「チュートリアル : データベースへのデータの保存 (複数テーブル)」を参照してください。
同時実行制御
データセットはデータ ソースから切断されているため、データ ソースのレコードにロックを保持できません。したがって、データベースを更新する場合に同時実行制御を維持することがアプリケーションにとって重要であると、データベースのレコードによってデータセットのレコードを調整する必要があります。たとえば、最後にデータセットを更新した後でデータベースのレコードが変更されていることがわかったとします。その場合は、アプリケーションに適したロジックを実行し、データベースのレコードや変更されたレコードに対応するデータセット内のレコードの処理方法を指定する必要があります。詳細については、「ADO.NET における同時実行制御」を参照してください。
参照
処理手順
方法 : TableAdapter を使用してデータを更新する