I had to accept the fact that the RowChange event is raised for every row during the DataTable Merge. This means that I couldn't use the RowChange Event to raise an event for my business classes to handle (which would then raise change notification events that refresh the UI when applicable).
To solve my problem I:
- Copy the original table
- Retrieve the data from the database and perform the necessary merge logic
- Compared the original table rows to the rows retrieved from the database and selected any row's whose RowVersion column changed
- Raised a "RowModified" event for any rows that were changed in the database and catch these events in my business classes.
The following code is a demonstration of how I solved my problem on a simpler scale based on my previous demo project I mentioned in my problem.
Here is the XAML markup for the MainWindow in my WPF sample/demo project:
<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>
And here is the code for the Button Click event and the class that is used to demonstrate the merging of data.
MainWindow Code for Button Click event:
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:
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
Thank you @Bjørn-Roger Kringsjå for helping me figure out how to properly merge data tables in this thread: Merge two identical DataTables results in DataRowState.Modified
I really hope this helps others to understand this topic.