鹤壁信息网
历史
当前位置:首页 > 历史

DataSet中的数据并发性异常

发布时间:2019-09-13 19:19:41 编辑:笔名

解决方法

有两种途径解决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块,直到无穷。更好的解决方法是将这段代码放入一个独立的在多个并发性异常发生的情况下可以调用的程序中。

饭后恶心精神焦虑抑郁消化不良
丁桂薏芽健脾凝胶价格
婴儿发烧手脚冰凉怎么回事
新生儿黄疸有何症状