This is more a conceptual question. Here is my current predicament; I am writing a vb.net WPF application and using the MVVM pattern (love it! maintainability is just amazingly awesome). Currently all the code is written by hand and there is no use of NHibernate or Entity Framework as the backend is an access database (due to policy I cannot use NH and EF doesn't support JET Databases, we may switch to MSSQL at some point but that may be a while from now).
The application is running quite well and was wondering what is the best way to send updates back to the database.
Currently the method is to add a boolean to a record on the set portion of the model to "dirty" then when update is pressed we loop through all the records that are "dirty" and use oledbcommand (execute with parameters) sql statements to update.
This create an excellent separation of concerns but if this is the wrong way I would like to know alternatives (please note the database type and the associated drawbacks such is it not working with EF).
Thanks!
Final code in VB.NET after comments etc:
Public Class Car
Implements ICloneable
Public Property Make() As String
Get
Return m_Make
End Get
Set(ByVal value As String)
m_Make = value
End Set
End Property
Private m_Make As String
Public Property Model() As String
Get
Return m_Model
End Get
Set(ByVal value As String)
m_Model = value
End Set
End Property
Private m_Model As String
Public Function Clone() As Object Implements System.ICloneable.Clone
Return New Car() With { _
.Make = Me.Make, _
.Model = Me.Model _
}
End Function
End Class
Public Class CarEqualityComparer
Implements IEqualityComparer(Of Car)
Public Overloads Function Equals(ByVal x As Car, ByVal y As Car) As Boolean Implements System.Collections.Generic.IEqualityComparer(Of Car).Equals
Return x.Make = y.Make AndAlso x.Model = y.Model
End Function
Public Overloads Function GetHashCode(ByVal obj As Car) As Integer Implements System.Collections.Generic.IEqualityComparer(Of Car).GetHashCode
Return 1 'http://blogs.msdn.com/b/jaredpar/archive/2008/06/03/making-equality-easier.aspx
End Function
End Class
Public Class CarRepository
Private _carComparator As New CarEqualityComparer
Private _cars As New ChangeTracker(Of Car)(_carComparator)
Public Function GetCars() As IEnumerable(Of Car)
'TODO: JET/ADO code here, you would obviously do in a for/while loop
Dim dbId1 As Integer = 1
Dim make1 As String = "Ford"
Dim model1 As String = "Focus"
Dim dbId2 As Integer = 2
Dim make2 As String = "Hyundai"
Dim model2 As String = "Elantra"
'TODO: create or update car objects
Dim car1 As Car
If Not _cars.IsTracking(dbId1) Then
car1 = New Car()
Else
car1 = _cars.GetItem(dbId1)
End If
car1.Make = make1
car1.Model = model1
If Not _cars.IsTracking(dbId1) Then
_cars.StartTracking(dbId1, car1)
End If
Dim car2 As Car
If Not _cars.IsTracking(dbId2) Then
car2 = New Car()
Else
car2 = _cars.GetItem(dbId2)
End If
car2.Make = make2
car2.Model = model2
If Not _cars.IsTracking(dbId2) Then
_cars.StartTracking(dbId2, car2)
End If
Return _cars.GetTrackedItems()
End Function
Public Sub SaveCars(ByVal cars As IEnumerable(Of Car))
'TODO: JET/ADO code here to update the item
Console.WriteLine("Distinct " & cars.Distinct.Count.ToString)
For Each changedItem As Car In _cars.GetChangedItems().Intersect(cars)
Console.Write("Saving: ")
Console.WriteLine(changedItem.Make)
Next
For Each newItem As Car In cars.Except(_cars.GetTrackedItems())
Console.Write("Adding: ")
Console.WriteLine(newItem.Make)
Dim newId As Integer = CInt(Math.Ceiling(Rnd() * 5000)) 'Random right now but JET/ADO to get the id later....
_cars.StartTracking(newId, newItem)
Next
Dim removalArray As New ArrayList
For Each deletedItem As Car In _cars.GetTrackedItems().Except(cars)
Console.Write("Removing: ")
Console.WriteLine(deletedItem.Make)
removalArray.Add(_cars.GetId(deletedItem)) 'Cannot remove right as iterating through array - clearly that would be problematic....
Next
For Each dbId As Integer In removalArray
_cars.StopTracking(dbId)
Next
_cars.SetNewCheckpoint()
End Sub
End Class
Public Class ChangeTracker(Of T As {ICloneable})
'item "checkpoints" that are internal to this list
Private _originals As New Dictionary(Of Integer, T)()
Private _originalIndex As New Dictionary(Of T, Integer)()
'the current, live-edited objects
Private _copies As New Dictionary(Of Integer, T)()
Private _copyIndex As New Dictionary(Of T, Integer)()
Private _comparator As System.Collections.Generic.IEqualityComparer(Of T)
Public Sub New(ByVal comparator As System.Collections.Generic.IEqualityComparer(Of T))
_comparator = comparator
End Sub
Public Function IsChanged(ByVal copy As T) As Boolean
Dim original = _originals(_copyIndex(copy))
Return Not _comparator.Equals(copy, original)
End Function
Public Function GetChangedItems() As IEnumerable(Of T)
Dim items As IEnumerable(Of T)
items = _copies.Values.Where(Function(c) IsChanged(c))
Return items
End Function
Public Function GetTrackedItems() As IEnumerable(Of T)
Return _copies.Values
End Function
Public Sub SetNewCheckpoint()
For Each copy In Me.GetChangedItems().ToList()
Dim dbId As Integer = _copyIndex(copy)
Dim oldOriginal = _originals(dbId)
Dim newOriginal = DirectCast(copy.Clone(), T)
_originals(dbId) = newOriginal
_originalIndex.Remove(oldOriginal)
_originalIndex.Add(newOriginal, dbId)
Next
End Sub
Public Sub StartTracking(ByVal dbId As Integer, ByVal item As T)
Dim newOriginal = DirectCast(item.Clone(), T)
_originals(dbId) = newOriginal
_originalIndex(newOriginal) = dbId
_copies(dbId) = item
_copyIndex(item) = dbId
End Sub
Public Sub StopTracking(ByVal dbId As Integer)
Dim original = _originals(dbId)
Dim copy = _copies(dbId)
_copies.Remove(dbId)
_originals.Remove(dbId)
_copyIndex.Remove(copy)
_originalIndex.Remove(original)
End Sub
Public Function IsTracking(ByVal dbId As Integer) As Boolean
Return _originals.ContainsKey(dbId)
End Function
Public Function IsTracking(ByVal item As T) As Boolean
Return _copyIndex.ContainsKey(item)
End Function
Public Function GetItem(ByVal dbId As Integer) As T
Return _copies(dbId)
End Function
Public Function GetId(ByVal item As T) As Integer
Dim dbId As Integer = (_copyIndex(item))
Return dbId
End Function
End Class