为使用DataTable.Merge(DataTable)时未更改的行引发的DataTable RowChanged事件

时间:2014-02-28 17:47:47

标签: .net wpf vb.net datatable merge

我正在实施3层应用程序(WPF / UI层,业务层和数据访问层)。

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

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

我正在考虑使用DataTable.RowChanged Event进行此过程,但我发现的是,当我执行DataTable.Merge(DataTable) Method时,为DataTable中的每一行引发RowChanged事件,即使有没有变化。

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

我创建了一个名为TestingRowChangeAndMerge的WPF应用程序。此项目中有一个名为“MainWindow”的WPF窗口和一个名为“TableManager”的类,它创建了两个表,这些表将在调用“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>

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

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后,我再也无法使用Modified RowState,因为RowStates不再更改。

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

我如何知道实际更改了哪些行,因为所有行都会在合并中引发“RowChanged”事件???

1 个答案:

答案 0 :(得分: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>

以下是Button Click事件的代码以及用于演示数据合并的类。

Button Click事件的MainWindow代码:

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-RogerKringsjå帮助我弄清楚如何在此主题中正确合并数据表:Merge two identical DataTables results in DataRowState.Modified

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