演练:处理并发异常
当两个用户同时尝试更改数据库中的相同数据时会引发并发异常 (DBConcurrencyException)。 在本演练中,将创建一个 Windows 应用程序来阐释如何捕获 DBConcurrencyException、定位引起错误的行并给出一个处理错误的策略。
本演练将指导您进行以下过程:
创建新的**“Windows 应用程序”**项目。
根据 Northwind Customers 表创建新的数据集。
创建具有 DataGridView 的窗体来显示数据。
用 Northwind 数据库中的 Customers 表的数据填充数据集。
填充数据集后,使用 Visual Studio 中的 Visual Database Tools 直接访问 Customers 数据表并更改记录。
然后在该窗体中,将同一记录更改为不同的值,更新数据集,并尝试将更覆盖入数据库,这将导致引发并发错误。
捕获错误并显示不同版本的记录,以便用户可以确定是继续更新数据库还是取消更新。
系统必备
若要完成本演练,您需要:
- 能够访问 Northwind 示例数据库并具有执行更新的权限。 有关更多信息,请参见 如何:安装示例数据库。
备注
显示的对话框和菜单命令可能会与“帮助”中的描述不同,具体取决于您现用的设置或版本。若要更改设置,请在“工具”菜单上选择“导入和导出设置”。有关更多信息,请参见 Visual Studio 设置。
创建新项目
从创建新的 Windows 应用程序开始演练。
创建新的 Windows 应用程序项目
从**“文件”**菜单创建一个新的项目。
在**“项目类型”**窗格中选择一种编程语言。
在**“模板”窗格中选择“Windows 应用程序”**。
将项目命名为 ConcurrencyWalkthrough,然后单击**“确定”**。
Visual Studio 随即将该项目添加到**“解决方案资源管理器”**,并在设计器中显示一个新窗体。
创建 Northwind 数据集
本节中将创建名为 NorthwindDataSet 的数据集。
创建 NorthwindDataSet
从**“数据”菜单中选择“添加新数据源”**。
数据源配置向导随即打开。
在**“选择数据源类型”页面上选择“数据库”**。
从可用连接列表中选择一个到 Northwind 示例数据库的连接,如果连接列表中的连接不可用则单击**“新建连接”**。
备注
如果要连接到本地数据库文件,则当询问是否将文件添加到项目中时,选择“否”。
在**“将连接字符串保存到应用程序配置文件”页面上单击“下一步”**。
展开**“Tables”**节点并选择 Customers 表。 数据集的默认名称应为 NorthwindDataSet。
单击**“完成”**将数据集添加到项目中。
创建数据绑定 DataGridView 控件
在本节中,将通过把**“Customers”项从“数据源”**窗口拖动到 Windows 窗体上来创建 DataGridView。
创建绑定到 Customers 表的 DataGridView 控件
从**“数据”菜单中选择“显示数据源”来打开“数据源”**窗口。
从**“数据源”窗口展开“NorthwindDataSet”节点并选择“Customers”**表。
单击表节点上的下拉箭头并从下拉列表中选择**“DataGridView”**。
将表拖动到窗体上的空白区域中。
一个名为 CustomersDataGridView 的 DataGridView 控件和一个名为 CustomersBindingNavigator 的 BindingNavigator 被添加到绑定到 BindingSource(它进而被绑定到 NorthwindDataSet 中的 Customers 表)的窗体中。
检查点
现在可以测试窗体,以确定此时它的行为与预期相同。
测试窗体
按 F5 运行该应用程序
窗体出现,上面带有一个用 Customers 表中的数据填充的 DataGridView 控件。
从**“调试”菜单中选择“停止调试”**。
处理并发错误
处理错误的方式取决于应用程序所遵循的特定业务规则。 对于本演练,为了举例说明,在引发并发冲突后将使用下面的策略来处理并发错误:
应用程序将为用户显示记录的三个版本:
数据库中的当前记录。
加载到数据集中的原始记录。
数据集中的建议更改。
然后用户就能够使用建议版本覆盖数据库,或是取消更新并用数据库中的新值刷新数据集。
启用并发错误的处理
创建自定义错误处理程序。
向用户显示选项。
处理用户的响应。
重新发送更新,或重新设置数据集中的数据。
添加用于处理并发异常的代码
当尝试执行更新且引发了异常时,通常会希望利用异常所提供的信息。
在本节中,将添加代码,以尝试更新数据库,并处理可能引发的任何 DBConcurrencyException 以及任何其他异常。
备注
本演练的后面部分将添加 CreateMessage 和 ProcessDialogResults 方法。
添加并发错误的错误处理程序
将以下代码添加到 Form1_Load 方法下:
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
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."); } }
替换 CustomersBindingNavigatorSaveItem_Click 方法以调用 UpdateDatabase 方法,使其形式如下所示:
Private Sub CustomersBindingNavigatorSaveItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles CustomersBindingNavigatorSaveItem.Click UpdateDatabase() End Sub
private void customersBindingNavigatorSaveItem_Click(object sender, EventArgs e) { UpdateDatabase(); }
向用户显示选项
您刚编写的代码将调用 CreateMessage 过程来向用户显示错误信息。 对于本演练,您将使用一个消息框来向用户显示记录的不同版本,并让用户选择是用新更改覆盖记录还是取消编辑。 用户选择该消息框上的选项(单击按钮)后,响应将传递到 ProcessDialogResult 方法。
创建要向用户显示的消息
在**“代码编辑器”**中添加下面的代码来创建该消息。 在 UpdateDatabase 方法下输入此代码。
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
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; }
处理用户的响应
您还需要用代码来处理用户对消息框的响应。 可以用建议的更改覆盖数据库中的当前记录,也可以放弃本地更改并用数据库中的当前记录刷新数据表。 如果用户选择“是”,则将 preserveChanges 参数设置为 true 来调用 Merge 方法。 由于记录的初始版本现在与数据库中的记录匹配,所以更新尝试将会成功。
处理消息框中的用户输入
在上一节所添加的代码下添加下面的代码。
' 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
// 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; } }
测试
现在可以测试窗体,以确保其行为与预期相同。 要模拟并发冲突,需要在填充 NorthwindDataSet 之后更改数据库中的数据。
测试窗体
按 F5 运行该应用程序。
窗体出现后,使其保持运行并切换到 Visual Studio IDE。
从**“视图”菜单中选择“服务器资源管理器”**。
在**“服务器资源管理器”中,展开您的应用程序正在使用的连接,然后展开“Tables”**节点。
右击**“Customers”表并选择“显示表数据”**。
在第一条记录 (ALFKI) 中,将 ContactName 更改为 Maria Anders2。
备注
导航至另一行以提交更改。
切换到 ConcurrencyWalkthrough 的运行窗体。
在窗体上的第一条记录 (ALFKI) 中,将 ContactName 更改为 Maria Anders1。
单击**“保存”**按钮。
引发并发错误,并出现消息框。
单击**“否”会取消更新,并用数据库中的当前值更新数据集,而单击“是”**则会将建议值写入数据库。
请参见
概念
Visual Studio 2012 中针对数据应用程序开发的新增功能
在 Visual Studio 中将 Windows 窗体控件绑定到数据