演练:处理并发异常

更新:2007 年 11 月

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

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

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

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

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

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

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

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

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

先决条件

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

说明:

显示的对话框和菜单命令可能会与“帮助”中的描述不同,具体取决于您的当前设置或版本。若要更改设置,请在“工具”菜单上选择“导入和导出设置”。有关更多信息,请参见 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. 将表拖动到窗体上的空白区域中。

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

检查点

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

测试窗体

  1. 按 F5 运行该应用程序

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

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

处理并发错误

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

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

  • 数据库中的当前记录。

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

  • 数据集中的建议更改。

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

启用并发错误的处理

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

  2. 向用户显示选项。

  3. 处理用户的响应。

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

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

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

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

说明:

本演练的后面部分将添加 CreateMessage 和 ProcessDialogResults 方法。

添加并发错误的错误处理程序

  1. 将以下代码添加到 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.");
        }
    }
    
  2. 替换 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.Item(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:
                UpdateDatabase();
                break;
    
            case DialogResult.No:
                northwindDataSet.Merge(tempCustomersDataTable);
                MessageBox.Show("Update cancelled");
                break;
        }
    }
    

测试

现在可以测试窗体,以确保其行为与预期相同。要模拟并发冲突,需要在填充 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 应用程序中的窗体上显示数据

在应用程序中编辑数据

验证数据

保存数据