【问题标题】:DataTable RowChanged Events raised for rows that have not changed when using DataTable.Merge(DataTable)DataTable RowChanged 使用 DataTable.Merge(DataTable) 时为未更改的行引发的事件
【发布时间】:2014-04-01 20:58:23
【问题描述】:

我正在实现一个 3 层应用程序(WPF/UI 层、业务层和数据访问层)。

在数据访问层中,我有一个刷新方法,用于查询数据库并将该查询中的行合并到包含业务层对象信息的 DataTables 中。

业务层对象需要检测其对应的行何时发生更改,以便它可以引发通知属性事件,让 UI 知道信息已更改并刷新显示。

我曾考虑在此过程中使用DataTable.RowChanged Event,但我发现当我执行DataTable.Merge(DataTable) Method 时,即使没有更改,也会为 DataTable 中的每一行引发 RowChanged 事件。

出于演示目的,我已经简化了我在内存 DataTables 中的问题使用,并发现同样的事情发生了。

我创建了一个名为 TestingRowChangeAndMerge 的 WPF 应用程序。此项目中有一个名为“MainWindow”的 WPF 窗口和一个名为“TableManager”的类,该类创建 2 个表,当调用“Merge”方法时将合并这些表,并具有指示在此期间引发的 RowChanged 事件数的属性合并过程。

这是我的 MainWindow 的 XAML:

<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:TestingRowChangeAndMerge"
    Title="MainWindow" Height="350" Width="525">
<Window.Resources>
    <local:TableManger x:Key="TableManagerResource" />
</Window.Resources>
<Grid DataContext="{StaticResource TableManagerResource}">
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <DataGrid ItemsSource="{Binding Table1.DefaultView}" AutoGenerateColumns="True" >

    </DataGrid>
    <StackPanel Grid.Row="1">
        <TextBlock Text="{Binding ChangesDetected}" />
    <Button x:Name="MergeTables" Content="Merge" Click="MergeTables_Click"/>
    </StackPanel>
</Grid>

这是用于处理按钮单击事件的 MainWindow.xaml.VB 代码以及 TableManager 类:

Class MainWindow 

Private Sub MergeTables_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)
    Dim tbManager As TableManger = FindResource("TableManagerResource")
    tbManager.MergeTables()
End Sub

End Class

Class TableManger
Implements ComponentModel.INotifyPropertyChanged

Private _table1 As System.Data.DataTable
Private _table2 As System.Data.DataTable
Private _changesDetected As Integer = 0

Public ReadOnly Property Table1
    Get
        Return _table1
    End Get
End Property
Public ReadOnly Property ChangesDetected As Integer
    Get
        Return _changesDetected
    End Get
End Property

Public Sub New()
    _table1 = CreateTableWithData()
    _table1.AcceptChanges()

    AddHandler _table1.RowChanged, New System.Data.DataRowChangeEventHandler(AddressOf Row_Changed)
End Sub

Public Sub MergeTables()

    _table2 = _table1.Clone
    Dim tableRows As New List(Of System.Data.DataRow)
    For Each r In _table1.Rows
        Dim dr2 = _table2.NewRow
        For Each col As System.Data.DataColumn In _table1.Columns
            dr2(col.ColumnName) = r(col.ColumnName)
        Next
        _table2.Rows.Add(dr2)
        tableRows.Add(dr2)
    Next
    _table2.AcceptChanges()


    If _table2.Rows.Count > 0 Then
        _table2.Rows(0)(1) = "TB2 Changed"
    End If

    If _table1.Rows.Count > 0 Then
        '_table1.Rows(0)(1) = "TB1 Change"'
        _table1.Rows(1)(1) = "TB1 Change"
    End If

    _changesDetected = 0
    Dim perserveChanges As Boolean = True
    Dim msAction As System.Data.MissingSchemaAction = System.Data.MissingSchemaAction.Ignore

    Dim changes As System.Data.DataTable = _table1.GetChanges()
    If changes IsNot Nothing Then
        changes.Merge(_table2, perserveChanges, msAction)
        _table1.Merge(changes, False, msAction)
    Else
        _table1.Merge(_table2, False, msAction)
    End If


    MessageBox.Show(String.Format("Changes in Change Table: {0} {1}Changes Detected: {2}", If((changes Is Nothing), 0, changes.Rows.Count), System.Environment.NewLine, _changesDetected), "Testing")

    RaiseEvent PropertyChanged(Me, New ComponentModel.PropertyChangedEventArgs("Table1"))
    RaiseEvent PropertyChanged(Me, New ComponentModel.PropertyChangedEventArgs("ChangesDetected"))
End Sub

Private Sub Row_Changed(ByVal sender As Object, ByVal e As System.Data.DataRowChangeEventArgs)
    Select Case e.Action
        Case System.Data.DataRowAction.Change'
        '    If e.Row.RowState <> System.Data.DataRowState.Unchanged Then'
               _changesDetected += 1
        '    End If
    End Select
End Sub

Private Function CreateTableWithData() As System.Data.DataTable
    Dim newTable As New System.Data.DataTable
    Dim columnID As New System.Data.DataColumn("ID", GetType(Guid))
    Dim columnA As New System.Data.DataColumn("ColumnA", GetType(String))
    Dim columnB As New System.Data.DataColumn("ColumnB", GetType(String))
    newTable.Columns.AddRange({columnID, columnA, columnB})
    newTable.PrimaryKey = {newTable.Columns(0)}
    For i = 0 To 5
        Dim dr = newTable.NewRow
        dr("ID") = Guid.NewGuid
        dr("ColumnA") = String.Format("Column A Row {0}", i.ToString)
        dr("ColumnB") = String.Format("Column B Row {0}", i.ToString)
        newTable.Rows.Add(dr)
    Next
    Return newTable
End Function

Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
End Class

当您单击按钮时,您将看到按钮上方显示的数字从 0 变为 6,这反映了执行表合并时引发的 RowChangedEvents 的数量。

如何更改它以便我可以检测到实际行更改何时发生??

编辑/更新:

我没有在原始代码中调用接受 table2 上的更改,这意味着当我将 table1 和 table2 合并在一起时,所有行都被标记为已修改。我已更新此处的代码以正常工作。

在我解决了这个问题之后,我更改了处理 Row_Change 事件的代码中的逻辑,以便它仅在行的 RowState 不等于 Unchanged 时增加 _changesDetected 计数器,因为我认为这将指示何时行改变了。

但在回答Merge two identical DataTables results in DataRowState.Modified 后,我无法再使用修改后的 RowState,因为 RowStates 不再更改。

所以我回到我原来的问题/问题:

由于所有行都在合并中引发“RowChanged”事件,我怎么知道哪些行实际发生了更改???

【问题讨论】:

  • 看起来这与问题非常相关:Merge two identical DataTables results in DataRowState.Modified。但我没有得到任何答案或cmets。 ://
  • 刚刚测试过,你是对的:合并后行被标记为已修改!
  • +1 我希望你的“新问”问题会比我的问题引起更多关注;)
  • 啊哈我想通了这两个问题。我不得不在 table2 上调用 AcceptChanges 以防止更改行状态(有道理,它们被“添加”到表 2 中,因此在合并后所有行都被正确标记为“修改”)。现在,在 RowChanged 事件中,我只需检查 e.Row.RowState 以确定行的数据是否已更改!非常感谢您让我调查您的问题 :)
  • 我在看你的问题....这没有意义。立即试用您的代码

标签: .net wpf vb.net datatable merge


【解决方案1】:

我不得不接受在 DataTable Merge 期间为每一行引发 RowChange 事件的事实。这意味着我不能使用 RowChange 事件来引发一个事件以供我的业务类处理(这将引发适用时刷新 UI 的更改通知事件)。

为了解决我的问题,我:

  • 复制原表
  • 从数据库中检索数据并执行必要的合并逻辑
  • 将原始表行与从数据库中检索到的行进行比较,并选择其 RowVersion 列发生更改的任何行
  • 为数据库中更改的任何行引发“RowModified”事件,并在我的业务类中捕获这些事件。

以下代码演示了我如何根据我在问题中提到的之前的演示项目以更简单的规模解决我的问题。

这是我的 WPF 示例/演示项目中 MainWindow 的 XAML 标记:

<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:TestingRowChangeAndMerge"
    Title="MainWindow" Height="350" Width="525">
<Window.Resources>
    <local:TableManger x:Key="TableManagerResource" />
</Window.Resources>
<Grid DataContext="{StaticResource TableManagerResource}">
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <DataGrid ItemsSource="{Binding Table1.DefaultView}" AutoGenerateColumns="True" >

    </DataGrid>
    <StackPanel Grid.Row="1">
        <TextBlock Text="{Binding ChangesDetected}" />
    <Button x:Name="MergeTables" Content="Merge" Click="MergeTables_Click"/>
    </StackPanel>
</Grid>
</Window>

这里是按钮单击事件的代码和用于演示数据合并的类。

按钮点击事件的主窗口代码:

Class MainWindow

Private Sub MergeTables_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)
    Dim tbManager As TableManger = FindResource("TableManagerResource")
    tbManager.MergeTables()
End Sub

End Class

TableManger 类:

Class TableManger
Implements ComponentModel.INotifyPropertyChanged

Public Event RowModified(ByVal id As Guid)
Private _table1 As System.Data.DataTable
Private _changesDetected As Integer = 0

Public ReadOnly Property Table1
    Get
        Return _table1
    End Get
End Property
Public ReadOnly Property ChangesDetected As Integer
    Get
        Return _changesDetected
    End Get
End Property

Public Sub New()
    _table1 = CreateTableWithData()
    _table1.AcceptChanges()

    AddHandler Me.RowModified, AddressOf RowModifiedHandler
End Sub

Public Sub MergeTables()
    _changesDetected = 0

    Dim tablePopulatedFromDB = SimulateGetDataFromDatabase()

    'The following is used test that changes are preserved during merging'
    'If _table1.Rows.Count > 0 Then'
    '    ' _table1.Rows(0)(1) = "TB1 Change"'
    '    _table1.Rows(1)(1) = "Change made by user"'
    'End If'

    Dim originalTableData = _table1.Copy
    Dim perserveChanges As Boolean = True
    Dim msAction As System.Data.MissingSchemaAction = System.Data.MissingSchemaAction.Ignore


    Dim changes As System.Data.DataTable = _table1.GetChanges()
    If changes IsNot Nothing Then
        changes.Merge(tablePopulatedFromDB, perserveChanges, msAction)
        _table1.Merge(changes, False, msAction)
    Else
        _table1.Merge(tablePopulatedFromDB, False, msAction)
    End If

    Dim changedRows = From tb1Row In _table1.Rows
                      Join origTBRow In originalTableData.Rows
                      On origTBRow("ID") Equals tb1Row("ID")
                      Where origTBRow("RowVersion") <> tb1Row("RowVersion")
                      Select tb1Row
    For Each changedRow In changedRows
        RaiseEvent RowModified(changedRow("ID"))
    Next

    RaiseEvent PropertyChanged(Me, New ComponentModel.PropertyChangedEventArgs("Table1"))
    RaiseEvent PropertyChanged(Me, New ComponentModel.PropertyChangedEventArgs("ChangesDetected"))

    MessageBox.Show(String.Format("Changes Detected: {0}", _changesDetected), "Testing")
End Sub

Private Sub RowModifiedHandler(ByVal id As Guid)
    _changesDetected += 1
End Sub

Private Function SimulateGetDataFromDatabase() As System.Data.DataTable
    Dim dbtable = _table1.Copy
    If dbtable.Rows.Count > 0 Then
        dbtable.Rows(0)(1) = String.Format("Change in Database...Old RowVersion: {0}", dbtable.Rows(0)("RowVersion")) 'simulating a change in data retrieved from the database for the first row

        Dim newRVersion As Integer 'Whenever a row is updated the row version is incremented in the database
        If dbtable.Rows.Count > dbtable.Rows(0)("RowVersion") Then
            newRVersion = dbtable.Rows.Count + 1
        Else
            newRVersion = dbtable.Rows(0)("RowVersion") + 1
        End If
        dbtable.Rows(0)(2) = newRVersion
    End If
    dbtable.AcceptChanges()

    Return dbtable
End Function

Private Function CreateTableWithData() As System.Data.DataTable
    Dim newTable As New System.Data.DataTable
    Dim columnID As New System.Data.DataColumn("ID", GetType(Guid))
    Dim columnA As New System.Data.DataColumn("ColumnA", GetType(String))
    Dim columnB As New System.Data.DataColumn("RowVersion", GetType(Integer))
    newTable.Columns.AddRange({columnID, columnA, columnB})
    newTable.PrimaryKey = {newTable.Columns(0)}
    For i = 0 To 5
        Dim dr = newTable.NewRow
        dr("ID") = Guid.NewGuid
        dr("ColumnA") = String.Format("Column A Row {0}", i.ToString)
        dr("RowVersion") = i
        newTable.Rows.Add(dr)
    Next
    Return newTable
End Function

Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
End Class

感谢@Bjørn-Roger Kringsjå 帮助我弄清楚如何正确合并此线程中的数据表:Merge two identical DataTables results in DataRowState.Modified

我真的希望这有助于其他人理解这个主题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-08
    • 1970-01-01
    • 2014-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多