共用方式為


在 .NET Framework 應用程式中將資料儲存回資料庫

注意

資料集和相關類別是 2000 年代初的舊版 .NET Framework 技術,可讓應用程式在應用程式與資料庫中斷連線時使用記憶體中的資料。 這些技術特別適用於可讓使用者修改資料並將變更保存回資料庫的應用程式。 雖然已證明資料集是非常成功的技術,但建議新的 .NET 應用程式使用 Entity Framework Core。 Entity Framework 提供更自然的方式,將表格式資料作為物件模型使用,而且具有更簡單的程式設計介面。

資料集是資料的記憶體內部複本。 如果您修改該資料,最好將這些變更儲存回資料庫。 您可以使用下列三個方式之一來執行此動作:

  • 藉由呼叫 TableAdapter 的其中一個 Update 方法

  • 藉由呼叫 TableAdapter 的其中一個 DBDirect 方法

  • 藉由呼叫 TableAdapterManager 上的 UpdateAll 方法,其為當資料集包含與資料集中其他資料表相關的資料表時 Visual Studio 為您產生

當您將資料集資料表上的資料繫結至 Windows Form 或 XAML 頁面上的控制項時,資料繫結架構會為您執行所有工作。

如果您熟悉 TableAdapters,可以直接跳到下列其中一個主題:

主題 說明
在資料庫中插入新的資料錄 如何使用 TableAdapters 或 Command 物件執行更新和插入
使用 TableAdapter 更新資料 如何使用 TableAdapters 執行更新
階層式更新 如何使用兩個或多個相關資料表從資料集執行更新
處理並行例外狀況 當兩位使用者同時嘗試變更資料庫中相同的資料時,如何處理例外狀況
如何:使用交易儲存資料 如何在交易中儲存資料,使用 System. Transactions 命名空間和 TransactionScope 物件
儲存異動中的資料 建立 Windows Forms 應用程式的逐步解說,示範將資料儲存至交易內的資料庫
儲存資料至資料庫 (多個資料表) 如何編輯記錄,並將多個資料表中的變更儲存回資料庫
從物件中將資料儲存至資料庫 如何使用 TableAdapter DbDirect 方法,將資料從不在資料集中的物件傳遞至資料庫
使用 TableAdapter DBDirect 方法儲存資料 如何使用 TableAdapter 將 SQL 查詢直接傳送至資料庫
將資料集儲存為 XML 如何將資料集儲存至 XML 文件

兩個階段更新

更新資料來源是兩個步驟的程序。 第一個步驟是使用新的記錄、變更的記錄或刪除的記錄來更新資料集。 如果您的應用程式永遠不會將這些變更傳送回資料來源,則表示您已完成更新。

如果您將變更傳送回資料庫,則需要第二個步驟。 如果您未使用資料繫結控制項,則必須手動呼叫用來填入資料集的相同 TableAdapter (或資料配接器) 的 Update 方法。 不過,您也可以使用不同的配接器,例如,將資料從某個資料來源移至另一個資料來源,或更新多個資料來源。 如果您未使用資料繫結,而且在儲存相關資料表的變更,則必須手動具現化自動產生的 TableAdapterManager 類別的變數,然後呼叫其 UpdateAll 方法。

數據集更新的概念圖

資料集包含資料表集合,其中包含資料列的集合。 如果您想要稍後更新基礎資料來源,必須在新增或移除資料列時,在 DataTable.DataRowCollection 屬性上使用方法。 這些方法會執行更新資料來源所需的變更追蹤。 如果您在 Rows 屬性上呼叫 RemoveAt 集合,則該刪除不會傳回資料庫。

合併資料集

您可以更新資料集的內容,方法是將其與另一個資料集合併。 這牽涉到將來源資料集的內容複製到呼叫端資料集 (稱為目標資料集)。 合併資料集時,來源資料集中的新記錄會新增至目標資料集。 此外,來源資料集中的其他資料行會新增至目標資料集。 當您有本機資料集,並從另一個應用程式取得第二個資料集時,合併資料集很有用。 從 XML Web 服務等元件取得第二個資料集,或需要從多個資料集整合資料時,它也很有用。

合併資料集時,您可以傳遞 Boolean 引數 (preserveChanges),告知 Merge 方法是否要在目標資料集中保留現有的修改。 由於資料集會維護多個版本的記錄,因此請務必記得合併多個版本的記錄。 下表顯示兩個資料集中的記錄如何合併:

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

呼叫 Merge 方法並搭配 preserveChanges = true targetDataset.Merge(sourceDataset, true) 會產生下列資料:

DataRowVersion 目標資料集 來源資料集
原始 James C. Wilson James C. Wilson
目前 Jim Wilson James C. Wilson

警告

preserveChanges = true 案例中,如果在目標資料集的記錄上呼叫 RejectChanges 方法,則會還原為來自來源資料集的原始資料。 這表示如果您嘗試使用目標資料集來更新原始資料來源,它可能找不到要更新的原始資料列。 您可以防止並行違規,方法是使用來自資料來源的更新記錄填入另一個資料集,然後執行合併以防止並行違規。 (當另一位使用者在填入資料集之後修改資料來源中的記錄時,就會發生並行違規。)

更新限制式

若要對現有的資料列進行變更,請在個別資料行中新增或更新資料。 如果資料集包含限制式 (例如外部索引鍵或不可為 Null 的限制式),記錄可能會在您更新記錄時暫時處於錯誤狀態。 也就是說,在您完成更新一個資料行之後,但在到達下一個資料行之前,它可能處於錯誤狀態。

若要防止過早限制式違規,您可以暫時暫止更新限制式。 這有兩個目的:

  • 它可防止在您完成更新一個資料行但尚未開始更新另一個資料行時擲回錯誤。

  • 它可防止引發特定更新事件 (經常用於驗證的事件)。

注意

在 Windows Forms 中,資料格內建的資料繫結架構會暫止限制式檢查,直到焦點移出資料列,且您不需要明確呼叫 BeginEditEndEditCancelEdit 方法。

在資料集上叫用 Merge 方法時,會自動停用限制式。 當合併完成時,如果資料集上有任何限制式無法啟用,則會擲回 ConstraintException。 在此情況下,EnforceConstraints 屬性會設定為 false,,而且必須先解決所有限制式違規,才能將 EnforceConstraints 屬性重設為 true

完成更新之後,您可以重新啟用限制式檢查,這也會重新啟用更新事件並引發它們。

如需暫止事件的詳細資訊,請參閱在填入資料集時關閉限制式

資料集更新錯誤

更新資料集中的記錄時,可能會發生錯誤。 例如,您可能會不小心將錯誤類型的資料寫入資料行,或資料太長,或資料有其他完整性問題。 或者,您可能有應用程式特定的驗證檢查,可在更新事件的任何階段期間引發自訂錯誤。 如需詳細資訊,請參閱驗證資料集中的資料

維護變更的相關資訊

資料集中變更的相關資訊會以兩個方式維護:藉由標記資料列,指出它們已變更 (RowState),以及保留記錄的多個複本 (DataRowVersion)。 藉由使用此資訊,程序可以判斷資料集中已變更的內容,並可傳送適當的更新至資料來源。

RowState 屬性

DataRow 物件的 RowState 屬性是一個值,提供特定資料列狀態的相關資訊。

下表詳細說明 DataRowState 列舉的可能值:

DataRowState 值 描述
Added 資料列已新增為 DataRowCollection 的項目。 (處於此狀態的資料列沒有對應的原始版本,因為呼叫上一個 AcceptChanges 方法時不存在。)
Deleted 資料列已使用 DataRow 物件的 Delete 刪除。
Detached 資料列已建立,但不屬於任何 DataRowCollection 的一部分。 DataRow 物件在建立之後、在加入至集合之前,以及從集合中移除它之後隨即處於此狀態。
Modified 資料列中的資料行值已以某種方式變更。
Unchanged 自上次呼叫 AcceptChanges 之後,資料列尚未變更。

DataRowVersion 列舉

資料集會維護多個版本的記錄。 使用 Item[] 屬性或 DataRow 物件的 GetChildRows 方法擷取 DataRow 中找到的值時,會使用 DataRowVersion 欄位。

下表詳細說明 DataRowVersion 列舉的可能值:

DataRowVersion 值 描述
Current 記錄的目前版本包含自上次呼叫 AcceptChanges 以來已在記錄上執行的所有修改。 如果資料列已被刪除,則沒有目前的版本。
Default 記錄的預設值,如資料集結構描述或資料來源所定義。
Original 記錄的原始版本是記錄自上次在資料集中認可變更以來的複本。 實際上,這通常是從資料來源讀取的記錄版本。
Proposed 在更新期間中間 (也就是在呼叫 BeginEdit 方法與 EndEdit 方法之間) 暫時可用的記錄提議的版本。 您通常會存取處理常式中提議的記錄版本以取得事件,例如 RowChanging。 叫用 CancelEdit 方法會反轉變更,並刪除資料列提議的版本。

當更新資訊傳送至資料來源時,原始版本和目前版本很有用。 一般而言,當更新傳送至資料來源時,資料庫的新資訊會在記錄的目前版本。 來自原始版本的資訊可用來找出要更新的記錄。

例如,在記錄的主索引鍵變更的情況下,您需要一個方式在資料來源中找出正確的記錄,才能更新變更。 如果不存在原始版本,則記錄很可能會附加至資料來源,不僅會導致額外的垃圾記錄,而且會導致一筆不正確且過時的記錄。 這兩個版本也會用於並行控制。 您可以將原始版本與資料來源中的記錄進行比較,以判斷記錄自載入資料集後是否已變更。

當您在實際認可對資料集的變更之前需要執行驗證時,提議的版本會很有用。

即使記錄已變更,不一定會有該資料列原始或目前的版本。 當您將新資料列插入資料表時,沒有原始版本,只有目前的版本。 同樣地,如果您藉由呼叫資料表的 Delete 方法來刪除資料列,則會有原始版本,但沒有目前的版本。

您可以藉由查詢資料列的 HasVersion 方法來測試是否有特定版本的記錄存在。 要求資料行的值時,您可以將 DataRowVersion 列舉值當做選用引數傳遞,以存取記錄的任一版本。

取得變更的記錄

不更新資料集中的每個記錄是常見的做法。 例如,使用者可能會使用顯示許多記錄的 Windows Forms DataGridView 控制項。 不過,使用者可能只會更新一些記錄、刪除一筆記錄,然後插入新的記錄。 資料集和資料表會提供方法 (GetChanges) 只傳回已修改的資料列。

您可以使用資料表 (GetChanges) 的 GetChanges 方法或資料集 (GetChanges) 本身來建立已變更記錄的子集。 如果您呼叫資料表的方法,它會傳回只有已變更記錄的資料表複本。 同樣地,如果您在資料集上呼叫方法,您會取得新資料集,其中只有已變更的記錄。

GetChanges 本身會傳回所有已變更的記錄。 相反地,藉由將所需的 DataRowState 做為參數傳遞至 GetChanges 方法,您可以指定您想要的已變更記錄的子集:新增的記錄、標示要刪除的記錄、中斷連結的記錄或修改的記錄。

當您想要將記錄傳送至另一個元件進行處理時,取得已變更記錄的子集很有用。 您可以只取得元件所需的記錄,來減少與其他元件通訊的額外負荷,而不是傳送整個資料集。

認可資料集中的變更

在資料集中進行變更時,會設定已變更資料列的 RowState 屬性。 原始和目前版本的記錄會由 RowVersion 屬性建立、維護及提供給您。 儲存在這些變更的資料列屬性中的中繼資料,是傳送正確的更新至資料來源所需。

如果變更可反映資料來源的目前狀態,您就不再需要維護此資訊。 一般而言,資料集及其來源處於同步有兩次:

  • 緊接在將資訊載入資料集後,例如從來源讀取資料時。

  • 將變更從資料集傳送至資料來源之後 (但不是之前,因為您會遺失將變更傳送至資料庫所需的變更資訊)。

您可以呼叫 AcceptChanges 方法,將暫止的變更認可至資料集。 通常會在下列時候呼叫 AcceptChanges

  • 載入資料集之後。 如果您藉由呼叫 TableAdapter 的 Fill 方法來載入資料集,配接器會自動為您認可變更。 不過,如果您藉由將另一個資料集合併至資料集來載入資料集,則必須手動認可變更。

    注意

    您可以將配接器的 AcceptChangesDuringFill 屬性設定為 false,以防止配接器在呼叫 Fill 方法時自動認可變更。 如果設定為 false,則會將填入期間插入之每個資料列的 RowState 設定為 Added

  • 將資料集變更傳送至另一個程序之後,例如 XML Web 服務。

    警告

    以此方式認可變更會清除任何變更資訊。 在您完成執行需要應用程式知道資料集中所做的變更之前,請勿認可變更。

此方法會完成下列作業:

AcceptChanges 方法可在三個層級使用。 您可以在 DataRow 物件上呼叫它,以只認可該資料列的變更。 您也可以在 DataTable 物件上呼叫它,以認可資料表中的所有資料列。 最後,您可以在 DataSet 物件上呼叫它,以認可資料集所有資料表的所有記錄中的所有暫止的變更。

下表描述根據呼叫方法所在的物件會認可的變更:

方法 結果
System.Data.DataRow.AcceptChanges 只會在特定資料列上認可變更。
System.Data.DataTable.AcceptChanges 會在特定資料表中所有資料列上認可變更。
System.Data.DataSet.AcceptChanges 會在資料集中所有資料表的所有資料列上認可變更。

注意

如果您藉由呼叫 TableAdapter 的 Fill 方法來載入資料集,則不需要明確接受變更。 依預設,Fill 方法會在完成填入資料表之後呼叫 AcceptChanges 方法。

相關的方法 RejectChanges 會藉由將 Original 版本複製回記錄的 Current 版本,來復原變更的影響。 它也會將每個記錄的 RowState 設定回 Unchanged

資料驗證

若要驗證應用程式中的資料符合傳遞至其中的程序需求,您通常必須新增驗證。 這可能牽涉到檢查表單中的使用者輸入是否正確、驗證由另一個應用程式傳送至您的應用程式的資料,或甚至檢查元件內計算的資訊是否落在資料來源和應用程式需求的限制式內。

您可以透過數種方式來驗證資料:

  • 在商務層中,藉由將程式碼新增至應用程式來驗證資料。 資料集是您可以執行此動作的一個位置。 資料集提供後端驗證的一些優點,例如在資料行和資料列值變更時驗證變更的能力。 如需詳細資訊,請參閱驗證資料集中的資料

  • 在呈現層中,將驗證新增至表單。 如需詳細資訊,請參閱 Windows Forms 中的使用者輸入驗證

  • 在資料後端中,將資料傳送至資料來源,例如資料庫,並允許它接受或拒絕資料。 如果您使用的資料庫具有複雜的設備用於資料驗證功能並提供錯誤資訊,這可以是實用的方法,因為不論資料來自何處,您都可以驗證資料。 不過,此方法可能無法容納應用程式特定的驗證需求。 此外,讓資料來源驗證資料可能會導致對資料來源進行多次來回行程,視應用程式如何協助解決後端所引發的驗證錯誤而定。

    重要

    使用資料命令搭配設定為 TextCommandType 屬性時,請先仔細檢查從用戶端傳送的資訊,再將它傳遞至您的資料庫。 惡意使用者可能會嘗試傳送 (插入) 修改過或額外的 SQL 陳述式,以獲得未授權的存取或破壞資料庫。 將使用者輸入傳送至資料庫之前,請一律驗證資訊是否有效。 最佳做法是盡可能一律使用參數化查詢或預存程序。

將更新傳輸至資料來源

在資料集中進行變更之後,您可以將變更傳輸至資料來源。 最常見的做法是呼叫 TableAdapter 的 Update 方法 (或資料配接器)。 方法會逐一查看資料表中的每個記錄、判斷所需的更新類型 (更新、插入或刪除),如果有的話,然後執行適當的命令。

圖解說明如何進行更新,假設您的應用程式使用包含單一資料表的資料集。 應用程式會從資料庫擷取兩個資料列。 擷取之後,記憶體內部資料表看起來像這樣:

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

您的應用程式將 Nancy Buchanan 的狀態變更為「偏好」。由於此變更,該資料列的 RowState 屬性的值會從 Unchanged 變更為 Modified。 第一個資料列的 RowState 屬性值會維持 Unchanged。 資料表現在看起來像這樣:

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

現在您的應用程式會呼叫 Update 方法將資料集傳送至資料庫。 方法會接著檢查每個資料列。 對於第一個資料列,方法不會將 SQL 陳述式傳送至資料庫,因為該資料列自原本從資料庫擷取後未變更。

不過,對於第二個資料列,Update 方法會自動叫用正確的資料命令,並將其傳輸至資料庫。 SQL 陳述式的特定語法取決於基礎資料存放區支援的 SQL 方言。 但是,下列傳輸 SQL 陳述式的一般特性值得注意:

  • 傳輸的 SQL 陳述式是 UPDATE 陳述式。 配接器知道使用 UPDATE 陳述式,因為 RowState 屬性的值是 Modified

  • 傳輸的 SQL 陳述式包含 WHERE 子句,指出 UPDATE 陳述式的目標是資料列,其中 CustomerID = 'c400'SELECT 陳述式的這個部分會區分目標資料列與所有其他資料列,因為 CustomerID 是目標資料表的主索引鍵。 WHERE 子句的資訊衍生自記錄的原始版本 (DataRowVersion.Original),以防識別資料列所需的值已變更。

  • 傳輸的 SQL 陳述式包含 SET 子句,可設定已修改的資料行的新值。

    注意

    如果 TableAdapter 的 UpdateCommand 屬性已設定為預存程序的名稱,配接器就不會建構 SQL 陳述式。 相反地,它會使用傳入的適當參數叫用預存程序。

傳遞參數

您通常會使用參數來傳遞即將在資料庫中更新的記錄的值。 當 TableAdapter 的 Update 方法執行 UPDATE 陳述式,它必須填入參數值。 它會從適當資料命令的 Parameters 集合取得這些值,在此案例中為 TableAdapter 中的 UpdateCommand 物件。

如果您使用 Visual Studio 工具來產生資料配接器,UpdateCommand 物件會包含對應至陳述式中每個參數預留位置的參數集合。

每個參數的 System.Data.SqlClient.SqlParameter.SourceColumn 屬性會指向資料表中的資料行。 例如,au_idOriginal_au_id 參數的 SourceColumn 屬性會設定為資料表中包含作者識別碼的任何資料行。當配接器的 Update 方法執行,它會從要更新的記錄讀取作者識別碼資料行,並將值填入陳述式中。

UPDATE 陳述式中,您必須指定新的值 (將寫入記錄的值) 以及舊值 (以便可以在資料庫中找到記錄)。 因此,每個值都有兩個參數:一個用於 SET 子句,另一個用於 WHERE 子句。 這兩個參數都會從要更新的記錄讀取資料,但會根據參數的 SourceVersion 屬性取得不同版本的資料行值。 SET 子句的參數會取得目前的版本,而 WHERE 子句的參數則會取得原始版本。

注意

您也可以自行在程式碼中設定 Parameters 集合中的值,這通常會在資料配接器 RowChanging 事件的事件處理常式中執行。