演练:处理并发异常

当两个用户同时尝试更改数据库中的相同数据时会引发并发异常 (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. 重新发送更新,或重新设置数据集中的数据。

ms171936.collapse_all(zh-cn,VS.110).gif添加用于处理并发异常的代码

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

在本节中,将添加代码,以尝试更新数据库,并处理可能引发的任何 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();
    }
    

ms171936.collapse_all(zh-cn,VS.110).gif向用户显示选项

您刚编写的代码将调用 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;
    }
    

ms171936.collapse_all(zh-cn,VS.110).gif处理用户的响应

您还需要用代码来处理用户对消息框的响应。 可以用建议的更改覆盖数据库中的当前记录,也可以放弃本地更改并用数据库中的当前记录刷新数据表。 如果用户选择“是”,则将 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 之后更改数据库中的数据。

测试窗体

  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 2012 中针对数据应用程序开发的新增功能

在 Visual Studio 中将 Windows 窗体控件绑定到数据

准备应用程序以接收数据

将数据获取到应用程序

在 Visual Studio 中将控件绑定到数据

在应用程序中编辑数据

验证数据

保存数据

其他资源

数据演练

连接到 Visual Studio 中的数据