我正在实施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”事件???
答案 0 :(得分:1)
我不得不接受在DataTable Merge期间为每一行引发RowChange事件的事实。这意味着我无法使用RowChange事件来引发我的业务类处理的事件(这会引发更改通知事件,以便在适用时刷新UI)。
解决我的问题我:
以下代码演示了我如何根据我之前在我的问题中提到的演示项目以更简单的比例解决我的问题。
以下是我的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
我真的希望这有助于其他人理解这个话题。