逐步解說:處理並行存取例外狀況
發行︰ 2016年4月
當兩位使用者同時嘗試變更資料庫中的相同資料時,就會引發並行存取例外狀況 (DBConcurrencyException)。 在這個逐步解說中,您將建立 Windows 應用程式,以說明如何攔截 DBConcurrencyException、找出造成錯誤的資料列,以及處理錯誤的策略。
這個逐步解說引導您執行下列程序:
建立新的 [Windows 應用程式] 專案。
根據 Northwind
Customers
資料表,建立新資料集。建立含有 DataGridView 的表單,以便顯示資料。
將 Northwind 資料庫中
Customers
資料表的資料填入資料集。在填入資料集之後,使用 Visual Studio 中的 Visual Database Tools,直接存取
Customers
資料表,並變更資料錄。然後在表單上將相同資料錄變更為不同的值、更新資料集,以及嘗試將變更寫入資料庫,這樣會引發並行存取錯誤。
攔截錯誤,然後顯示不同版本的資料錄,讓使用者決定是要繼續更新資料庫,還是取消更新。
必要條件
若要完成這個逐步解說,您需要:
- 利用執行更新的權限來存取 Northwind 範例資料庫。 如需詳細資訊,請參閱如何:安裝範例資料庫。
注意
根據您目前使用的設定或版本,您所看到的對話方塊與功能表指令可能會與 [說明] 中描述的不同。 若要變更設定,請從 [工具] 功能表中選擇 [匯入和匯出設定]。 如需詳細資訊,請參閱Customizing Development Settings in Visual Studio。
建立新專案
建立新的 Windows 應用程式,開始這個逐步解說。
若要建立新的 Windows 應用程式專案
從 [檔案] 功能表中,建立新專案。
在 [專案類型] 窗格中,選取程式設計語言。
在 [範本] 窗格中,選取 [Windows 應用程式]。
將專案命名為
ConcurrencyWalkthrough
,再按 [確定]。Visual Studio 隨即將專案加入至 [方案總管],並在設計工具中顯示新表單。
建立 Northwind 資料集
在本節中,您將建立名為 NorthwindDataSet
的資料集。
若要建立 NorthwindDataSet
選擇 [資料] 功能表上的 [加入新資料來源]。
資料來源組態精靈隨即開啟。
請選取 [選擇資料來源類型] 頁面上的 [資料庫]。
從可用連接清單中,選取 Northwind 範例資料庫的連接;如果連接清單中沒有此連接,請按一下 [新增連接]。
注意
如果您要連接到本機資料庫檔案,當詢問您是否要將檔案加入至專案時,請選取 [否]。
按一下 [將連接字串儲存到應用程式組態檔] 頁面上的 [下一步]。
展開 [資料表] 節點,並選取
Customers
資料表。 資料集的預設名稱應該是NorthwindDataSet
。按一下 [完成],將資料集加入至專案。
建立資料繫結 DataGridView 控制項
在本節中,您將從 [資料來源] 視窗將 [Customers] 項目拖曳至 Windows Form 上,以建立 DataGridView。
若要建立繫結至 Customers 資料表的 DataGridView 控制項
從 [資料] 功能表中,選擇 [顯示資料來源] 以開啟 [資料來源] 視窗。
從 [資料來源] 視窗,展開 [NorthwindDataSet] 節點,並選取 [Customers] 資料表。
按一下資料表節點上的向下鍵,並從下拉式清單中選取 [DataGridView]。
將資料表拖曳至表單上的空白區域。
名為
CustomersDataGridView
的 DataGridView 控制項以及名為CustomersBindingNavigator
的 BindingNavigator 會加入到繫結至 BindingSource 的表單 (此來源接著會繫結至NorthwindDataSet
中的Customers
資料表)。
檢查點
您現在可以測試表單,確定到目前為止它的行為表現如預期般。
若要測試表單
請按 F5 以執行應用程式。
此表單出現時,會包含 DataGridView 控制項,且控制項中填入了
Customers
資料表的資料。從 [偵錯] 功能中表選擇 [停止偵錯]。
處理並行存取錯誤
如何處理錯誤,端視應用程式使用的特定商務規則 (Business Rule) 而定。 在此逐步解說中,當引發並行違規之後,將會使用下列處理此並行存取錯誤的策略來當做說明:
應用程式會將資料錄的三個版本提供給使用者:
資料庫中的目前資料錄。
載入資料集中的原始資料錄。
資料集中的建議變更。
然後,使用者將能夠以建議的版本覆寫資料庫,或是取消更新,並使用資料庫的新值來重新整理資料集。
若要啟用並行存取錯誤的處理
建立自訂錯誤處理常式。
將選擇顯示給使用者。
處理使用者的回應。
重新傳送更新,或重設資料集中的資料。
加入程式碼來處理並行存取例外狀況
當您嘗試執行更新,而引發例外狀況時,通常會想要對引發例外狀況所提供的資訊進行處理。
在本節中,您將會加入可嘗試更新資料庫的程式碼,以及處理任何可能會被引發的 DBConcurrencyException 及任何其他例外狀況。
注意
此逐步解說的後面內容中,將會加入 CreateMessage
和 ProcessDialogResults
方法。
若要加入並行存取錯誤的錯誤處理
將以下程式碼加入至
Form1_Load
方法之下:private void UpdateDatabase() { try { this.customersTableAdapter.Update(this.northwindDataSet.Customers); MessageBox.Show("Update successful"); } catch (DBConcurrencyException dbcx) { DialogResult response = MessageBox.Show(CreateMessage((NorthwindDataSet.CustomersRow) (dbcx.Row)), "Concurrency Exception", MessageBoxButtons.YesNo); ProcessDialogResult(response); } catch (Exception ex) { MessageBox.Show("An error was thrown while attempting to update the database."); } }
Private Sub UpdateDatabase() Try Me.CustomersTableAdapter.Update(Me.NorthwindDataSet.Customers) MsgBox("Update successful") Catch dbcx As Data.DBConcurrencyException Dim response As Windows.Forms.DialogResult response = MessageBox.Show(CreateMessage(CType(dbcx.Row, NorthwindDataSet.CustomersRow)), "Concurrency Exception", MessageBoxButtons.YesNo) ProcessDialogResult(response) Catch ex As Exception MsgBox("An error was thrown while attempting to update the database.") End Try End Sub
取代
CustomersBindingNavigatorSaveItem_Click
方法來呼叫UpdateDatabase
方法,讓它看起來與下面類似:private void customersBindingNavigatorSaveItem_Click(object sender, EventArgs e) { UpdateDatabase(); }
Private Sub CustomersBindingNavigatorSaveItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles CustomersBindingNavigatorSaveItem.Click UpdateDatabase() End Sub
將選擇顯示給使用者
您剛撰寫的程式碼會呼叫 CreateMessage
程序,向使用者顯示錯誤資訊。 在這個逐步解說中,您將使用訊息方塊來將資料錄的不同版本顯示給使用者,並且允許使用者選擇是要利用變更覆寫資料錄或是取消編輯。 一旦使用者選取訊息方塊上的選項 (按一下按鈕),回應便會傳送至 ProcessDialogResult
方法。
若要建立訊息以顯示給使用者
將下列程式碼加入至 [程式碼編輯器],以建立訊息。 在
UpdateDatabase
方法之下輸入此程式碼。private string CreateMessage(NorthwindDataSet.CustomersRow cr) { return "Database: " + GetRowData(GetCurrentRowInDB(cr), DataRowVersion.Default) + "\n" + "Original: " + GetRowData(cr, DataRowVersion.Original) + "\n" + "Proposed: " + GetRowData(cr, DataRowVersion.Current) + "\n" + "Do you still want to update the database with the proposed value?"; } //-------------------------------------------------------------------------- // This method loads a temporary table with current records from the database // and returns the current values from the row that caused the exception. //-------------------------------------------------------------------------- private NorthwindDataSet.CustomersDataTable tempCustomersDataTable = new NorthwindDataSet.CustomersDataTable(); private NorthwindDataSet.CustomersRow GetCurrentRowInDB(NorthwindDataSet.CustomersRow RowWithError) { this.customersTableAdapter.Fill(tempCustomersDataTable); NorthwindDataSet.CustomersRow currentRowInDb = tempCustomersDataTable.FindByCustomerID(RowWithError.CustomerID); return currentRowInDb; } //-------------------------------------------------------------------------- // This method takes a CustomersRow and RowVersion // and returns a string of column values to display to the user. //-------------------------------------------------------------------------- private string GetRowData(NorthwindDataSet.CustomersRow custRow, DataRowVersion RowVersion) { string rowData = ""; for (int i = 0; i < custRow.ItemArray.Length ; i++ ) { rowData = rowData + custRow[i, RowVersion].ToString() + " "; } return rowData; }
Private Function CreateMessage(ByVal cr As NorthwindDataSet.CustomersRow) As String Return "Database: " & GetRowData(GetCurrentRowInDB(cr), Data.DataRowVersion.Default) & vbCrLf & "Original: " & GetRowData(cr, Data.DataRowVersion.Original) & vbCrLf & "Proposed: " & GetRowData(cr, Data.DataRowVersion.Current) & vbCrLf & "Do you still want to update the database with the proposed value?" End Function '-------------------------------------------------------------------------- ' This method loads a temporary table with current records from the database ' and returns the current values from the row that caused the exception. '-------------------------------------------------------------------------- Private TempCustomersDataTable As New NorthwindDataSet.CustomersDataTable Private Function GetCurrentRowInDB( ByVal RowWithError As NorthwindDataSet.CustomersRow ) As NorthwindDataSet.CustomersRow Me.CustomersTableAdapter.Fill(TempCustomersDataTable) Dim currentRowInDb As NorthwindDataSet.CustomersRow = TempCustomersDataTable.FindByCustomerID(RowWithError.CustomerID) Return currentRowInDb End Function '-------------------------------------------------------------------------- ' This method takes a CustomersRow and RowVersion ' and returns a string of column values to display to the user. '-------------------------------------------------------------------------- Private Function GetRowData(ByVal custRow As NorthwindDataSet.CustomersRow, ByVal RowVersion As Data.DataRowVersion) As String Dim rowData As String = "" For i As Integer = 0 To custRow.ItemArray.Length - 1 rowData &= custRow.Item(i, RowVersion).ToString() & " " Next Return rowData End Function
處理使用者的回應
您也將需要程式碼來處理使用者對於訊息方塊的回應。 此時有兩個選項:使用建議變更,覆寫資料庫中的目前資料錄,或是放棄本機變更,並以資料庫中目前資料錄重新整理資料表。 如果使用者選擇是,Merge 方法會以設為 true
的 preserveChanges 引數呼叫。 這會讓更新得以成功,因為現在資料錄的原始版本與資料庫中的資料錄相符。
若要處理訊息方塊的使用者輸入
在上節中所加入的程式碼下方,加入下列程式碼。
// This method takes the DialogResult selected by the user and updates the database // with the new values or cancels the update and resets the Customers table // (in the dataset) with the values currently in the database. private void ProcessDialogResult(DialogResult response) { switch (response) { case DialogResult.Yes: northwindDataSet.Merge(tempCustomersDataTable, true, MissingSchemaAction.Ignore); UpdateDatabase(); break; case DialogResult.No: northwindDataSet.Merge(tempCustomersDataTable); MessageBox.Show("Update cancelled"); break; } }
' This method takes the DialogResult selected by the user and updates the database ' with the new values or cancels the update and resets the Customers table ' (in the dataset) with the values currently in the database. Private Sub ProcessDialogResult(ByVal response As Windows.Forms.DialogResult) Select Case response Case Windows.Forms.DialogResult.Yes NorthwindDataSet.Customers.Merge(TempCustomersDataTable, True) UpdateDatabase() Case Windows.Forms.DialogResult.No NorthwindDataSet.Customers.Merge(TempCustomersDataTable) MsgBox("Update cancelled") End Select End Sub
測試
您現在可以測試表單以確定它的行為表現如預期般。 為了模擬並行違規,您必須在填入 NorthwindDataSet 後變更資料庫中的資料。
若要測試表單
按 F5 執行應用程式。
表單出現之後,讓它繼續執行,並切換至 Visual Studio IDE。
從 [檢視] 功能表選擇 [伺服器總管]。
在 [伺服器總管] 中,展開應用程式使用的連接,然後展開 [資料表] 節點。
以滑鼠右鍵按一下 [Customers] 資料表,選取 [顯示資料表資料]。
在第一個資料錄 (
ALFKI
) 中,將ContactName
變更為Maria Anders2
。注意
巡覽至不同的資料列,認可變更。
切換至
ConcurrencyWalkthrough
的執行中表單。在表單上的第一筆資料錄 (
ALFKI
) 中,將ContactName
變更為Maria Anders1
。按一下 [儲存] 按鈕。
將會引發並行存取錯誤,並出現訊息方塊。
按一下 [否] 會取消更新,並以資料庫中目前的值更新資料集,按一下 [是] 則會將建議值寫入資料庫。
請參閱
資料逐步解說
將 Windows Form 控制項繫結至 Visual Studio 中的資料
連接至 Visual Studio 中的資料
準備您的應用程式以接收資料
將資料擷取至您的應用程式中
將控制項繫結至 Visual Studio 中的資料
在您的應用程式中編輯資料
驗證資料
儲存資料