演练:处理并发异常

 

发布日期: 2016年7月

当两个用户同时尝试更改数据库中的相同数据时会引发并发异常 (DBConcurrencyException)。 在本演练中,将创建一个 Windows 应用程序来阐释如何捕获 DBConcurrencyException、定位引起错误的行并给出一个处理错误的策略。

本演练将指导您进行以下过程:

  1. 创建新的**“Windows 应用程序”**项目。

  2. 根据 Northwind Customers 表创建新的数据集。

  3. 创建具有 DataGridView 的窗体来显示数据。

  4. 用 Northwind 数据库中的 Customers 表的数据填充数据集。

  5. 填充数据集后,使用 Visual Studio 中的 Visual Database Tools 直接访问 Customers 数据表并更改记录。

  6. 然后在该窗体中,将同一记录更改为不同的值,更新数据集,并尝试将更覆盖入数据库,这将导致引发并发错误。

  7. 捕获错误并显示不同版本的记录,以便用户可以确定是继续更新数据库还是取消更新。

系统必备

若要完成本演练,您需要:

备注

显示的对话框和菜单命令可能会与“帮助”中的描述不同,具体取决于您现用的设置或版本。 若要更改设置,请在“工具”菜单上选择“导入和导出设置”。 有关详细信息,请参阅Customizing Development Settings in Visual Studio

创建新项目

从创建新的 Windows 应用程序开始演练。

创建新的 Windows 应用程序项目

  1. 从**“文件”**菜单创建一个新的项目。

  2. 在**“项目类型”**窗格中选择一种编程语言。

  3. 在**“模板”窗格中选择“Windows 应用程序”**。

  4. 将项目命名为 ConcurrencyWalkthrough,然后单击**“确定”**。

    Visual Studio 随即将该项目添加到**“解决方案资源管理器”**,并在设计器中显示一个新窗体。

创建 Northwind 数据集

本节中将创建名为 NorthwindDataSet 的数据集。

创建 NorthwindDataSet

  1. 从**“数据”菜单中选择“添加新数据源”**。

    数据源配置向导随即打开。

  2. 在**“选择数据源类型”页面上选择“数据库”**。

  3. 从可用连接列表中选择一个到 Northwind 示例数据库的连接,如果连接列表中的连接不可用则单击**“新建连接”**。

    备注

    如果要连接到本地数据库文件,则当询问是否将文件添加到项目中时,选择“否”

  4. 在**“将连接字符串保存到应用程序配置文件”页面上单击“下一步”**。

  5. 展开**“Tables”**节点并选择 Customers 表。 数据集的默认名称应为 NorthwindDataSet

  6. 单击**“完成”**将数据集添加到项目中。

创建数据绑定 DataGridView 控件

在本节中,将通过把**“Customers”项从“数据源”**窗口拖动到 Windows 窗体上来创建 DataGridView

创建绑定到 Customers 表的 DataGridView 控件

  1. 从**“数据”菜单中选择“显示数据源”来打开“数据源”**窗口。

  2. 从**“数据源”窗口展开“NorthwindDataSet”节点并选择“Customers”**表。

  3. 单击表节点上的下拉箭头并从下拉列表中选择**“DataGridView”**。

  4. 将表拖动到窗体上的空白区域中。

    一个名为 CustomersDataGridViewDataGridView 控件和一个名为 CustomersBindingNavigatorBindingNavigator 被添加到绑定到 BindingSource(它进而被绑定到 NorthwindDataSet 中的 Customers 表)的窗体中。

检查点

现在可以测试窗体,以确定此时它的行为与预期相同。

测试窗体

  1. 按 F5 运行该应用程序

    窗体出现,上面带有一个用 Customers 表中的数据填充的 DataGridView 控件。

  2. 从**“调试”菜单中选择“停止调试”**。

处理并发错误

处理错误的方式取决于应用程序所遵循的特定业务规则。 对于本演练,为了举例说明,在引发并发冲突后将使用下面的策略来处理并发错误:

应用程序将为用户显示记录的三个版本:

  • 数据库中的当前记录。

  • 加载到数据集中的原始记录。

  • 数据集中的建议更改。

然后用户就能够使用建议版本覆盖数据库,或是取消更新并用数据库中的新值刷新数据集。

启用并发错误的处理

  1. 创建自定义错误处理程序。

  2. 向用户显示选项。

  3. 处理用户的响应。

  4. 重新发送更新,或重新设置数据集中的数据。

添加用于处理并发异常的代码

当尝试执行更新且引发了异常时,通常会希望利用异常所提供的信息。

在本节中,将添加代码,以尝试更新数据库,并处理可能引发的任何 DBConcurrencyException 以及任何其他异常。

备注

本演练的后面部分将添加 CreateMessageProcessDialogResults 方法。

添加并发错误的错误处理程序
  1. 将以下代码添加到 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
    
  2. 替换 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 方法将被调用,同时 preserveChanges 参数设置为 true。 由于记录的初始版本现在与数据库中的记录匹配,所以更新尝试将会成功。

处理消息框中的用户输入
  • 在上一节所添加的代码下添加下面的代码。

            // 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 之后更改数据库中的数据。

测试窗体

  1. 按 F5 运行该应用程序。

  2. 窗体出现后,使其保持运行并切换到 Visual Studio IDE。

  3. 从**“视图”菜单中选择“服务器资源管理器”**。

  4. 在**“服务器资源管理器”中,展开您的应用程序正在使用的连接,然后展开“Tables”**节点。

  5. 右击**“Customers”表并选择“显示表数据”**。

  6. 在第一条记录 (ALFKI) 中,将 ContactName 更改为 Maria Anders2

    备注

    导航至另一行以提交更改。

  7. 切换到 ConcurrencyWalkthrough 的运行窗体。

  8. 在窗体上的第一条记录 (ALFKI) 中,将 ContactName 更改为 Maria Anders1

  9. 单击**“保存”**按钮。

    引发并发错误,并出现消息框。

  10. 单击**“否”会取消更新,并用数据库中的当前值更新数据集,而单击“是”**则会将建议值写入数据库。

请参阅

数据演练
在 Visual Studio 中将 Windows 窗体控件绑定到数据
连接到 Visual Studio 中的数据
准备应用程序以接收数据
将数据获取到应用程序
在 Visual Studio 中将控件绑定到数据
在应用程序中编辑数据
验证数据
保存数据