科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件DataSet中的数据并发性异常

DataSet中的数据并发性异常

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

本文调查了数据并发性异常背后的通常起因,介绍了解决这些问题的技术

作者:陶刚编译 来源:yesky 2007年11月9日

关键字:

  • 评论
  • 分享微博
  • 分享邮件
 解决方法

  有两种途径解决DBConcurrencyException问题。第一种是确保它永不重现:我可以删除图1中代码使用的SqlCommandBuilder对象,把它们更换为数据适配器对象的UpdateCommand 属性的SqlCommand对象。我将在CommandText属性中建立带有WHERE条件的SQL语句,只进行主键而不是所有列的过虑。这样将排除所有并发性问题(假定主键不会改变)。

  但是这种技术带来了几个问题。首先,很明显要更多的代码,因为我还得为每个数据适配器的InsertCommand和 DeleteCommand属性建立SqlCommand对象。另外,如果下层数据库概要(schema)发生了变动,这些硬编码将带来新错误。如果使用SqlCommandBuilder对象,应用程序在运行时决定数据库概要,接受任何改变,相应地建立SQL语句。这不是解决并发性问题,而是完全的避免了该问题,使用户在不知不觉中覆盖了他人的更改。

Try
 daCustomer.Update(dsAllData.Tables("Customer"))
 Catch dbcEx As Data.DBConcurrencyException
 Dim dResult As DialogResult
 dResult = MessageBox.Show(messageString, _
 "Data Concurrency Exception Occurred", _
 MessageBoxButtons.YesNoCancel, MessageBoxIcon.Error, _
 MessageBoxDefaultButton.Button1, _
 MessageBoxOptions.DefaultDesktopOnly)
 If dResult = DialogResult.Yes Then
  '两个选择:填充整个表或者刷新该行
  'daCustomer.Fill(dsAllData.Tables("Customer"))
  UpdateRow("Customer", dbcEx.Row.Item("ID"))
 ElseIf dResult = DialogResult.No Then
  '保存新行的拷贝
  Dim drCopy As DataRow, drCurrent As DataRow
  drCopy = dsAllData.Tables("Customer").NewRow()
  Dim dc As DataColumn
  drCurrent = _
   dsAllData.Tables("Customer").Rows.Find(dbcEx.Row.Item("ID"))
  For Each dc In drCurrent.Table.Columns
   If dc.ReadOnly = False Then _
    drCopy.Item(dc.ColumnName) = drCurrent.Item(dc.ColumnName)
  Next

  '从数据库中获取当前值
  UpdateRow("Customer", dbcEx.Row.Item("ID"))

  '现在恢复用户输入的值并再次保存
  For Each dc In drCurrent.Table.Columns
   If dc.ReadOnly = False Then _
    drCurrent.Item(dc.ColumnName) = drCopy.Item(dc.ColumnName)
  Next

  daCustomer.Update(dsAllData.Tables("Customer"))
 End If
End Try

图7.捕获并发性异常

  图7显示了一个更好的捕捉该异常的方案。Try...Catch块捕捉了DBConcurrencyException并给用户显示一个标识该错误的消息窗口,给用户提供一个选择(图8所示)。这样我识别已经出现了一个并发性错误并有两个选择:我可以检索下层数据并显示给用户,强制他们再次作修改,或者我能简单地使用该用户指定的改变覆盖下层数据。这些选项都显示在消息框中(图8):


图8.处理数据并发性异常

Private Sub UpdateRow(ByVal TableName As String, ByVal ID As Integer)
 '获取到特定行的引用
 Dim dr As DataRow = dsAllData.Tables(TableName).Rows.Find(ID)

 '建立命令更新获取新的下层数据
 Dim cmd As New SqlClient.SqlCommand("SELECT * FROM " & TableName & _
" WHERE ID=" & ID.ToString(), connCustSvc)

 '打开连接并建立数据读取器(DataReader)
 connCustSvc.Open()
 Dim rdr As SqlClient.SqlDataReader = cmd.ExecuteReader()
 rdr.Read()

 '将新数据从数据库复制到数据行
 Dim dc As DataColumn
 For Each dc In dr.Table.Columns
  If dc.ReadOnly = False Then _
   dr.Item(dc.ColumnName) = rdr.Item(dc.ColumnName)
 Next

 '接受数据行中的改变
 dr.AcceptChanges()
 connCustSvc.Close()
End Sub

图9. UpdateRow程序更新缓冲的数据行

  如果用户决定查看新的下层改变并放弃他们的更改,你只需要简单地刷新存储在Customer数据表中的数据。因为DataGrid绑定到数据表,该表格将自动地显示新数据。为了刷新数据,你有两种选择:第一种是使用数据适配器daCustomer的Fill方法来简单地填充整个数据表。虽然这种技术能够实现,但是它的花费太大,因为本来你只需要刷新一行。我建立了一个叫UpdateRow的程序,它仅仅读入有问题的行的数据(图9)。使用UpdateRow程序时要注意我已经在参数集合中定义了被找到的数据行是单个的、整型关键字的列,如果该表有不同数据类型或者复合键,你必须重载UpdateRow来处理特定键的需求。在数据行和/或数据表用当前数据刷新后,DataGrid在引起并发性异常的行上显示一个小Error图标(图10)。


图10.DataGrid显示Error图标

  用户的另一个选择是忽略对下层数据库的更改,强迫他的改变生效。有多种方法可以实现该功能。第一种是SQL Server数据库上直接执行一个SQL语句来使数据表中的数据同步。尽管这种方法可以实现,但是它要求你在数据库更改时为重写所有的SQL语句。使用这种技术编写的SQL是使用的特定数据库版本的硬编码(hard-coded),丢失了数据适配器和数据集对象提供的抽象性。

  更好的选择是使用我前面所写的UpdateRow程序并且允许数据适配器处理更新。图9中的代码首先建立了含有新的更改的数据行的拷贝,接着调用UpdateRow程序从数据库中获得新数据。调用UpdateRow过程是必要的,这样你在试图再次执行更新时才不会接收到并发性异常。该代码接着迭代数据行中所有的列,使用用户提供的值更新最近检索到的值。最后,使用数据适配器更新下层数据库。

  这些代码解决方法都有一些潜在的问题。首先,默认情况下数据适配器的Update方法在第一个并发性异常时就会失败,不处理后面的数据行。这会导致数据的部分更新或者几个并发性异常,每一个由用户单独处理。图7中的代码显示了另一个潜在的异常,在强迫用户的更改到下层数据库的时候。有很小的机会出现另一个用户在调用UpdateRow程序和执行daCustomer的Update方法之间更改了下层数据库。这将产生一个未处理的DBConcurrencyException。一种可能的解决方法是把Update方法放入Try...Catch块,但是第二个Catch代码可能与第一个相似,并且能潜在的产生它自己的并发性异常,就需要更多的Try...Catch块,直到无穷。更好的解决方法是将这段代码放入一个独立的在多个并发性异常发生的情况下可以调用的程序中。
    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章