2012-03-14 6 views
5

これは概念的な質問です。ここに私の現在の苦境があります。私はvb.net WPFアプリケーションを作成してMVVMパターンを使用しています(メンテナンス性は驚くほど素晴らしいです)。現在のところ、すべてのコードは手作業で書かれており、バックエンドはアクセスデータベースであるため、NHibernateやEntity Frameworkは使用されていません(私はNHを使用できないため、EFはJETデータベースをサポートしていないため、それは今からしばらくあるかもしれません)。最大効率のためにMVVMパターンでレコードを更新する適切な方法

アプリケーションは非常にうまく実行されており、更新をデータベースに送り返す最善の方法は何か不思議でした。

現在、このメソッドは、モデルの設定部分のレコードにブール値を追加して「ダーティ」にし、更新を押すと「ダーティ」なすべてのレコードをループし、oledbcommand(パラメータで実行) SQL文を更新します。

これは非常に懸念されていますが、これが間違った方法であれば、私は代替案を知りたいと思います(データベースの種類と関連する欠点はEFでは機能しない点に注意してください)。

ありがとうございます!

コメントなどの後VB.NETでの最終的なコード:

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 

答えて

3

あなたをコミットする更新/保存]ボタンを使用しているので、データベースへの変更、私はどこ、リポジトリのようなパターンを使用することをお勧めしますリポジトリは、保存操作を実行するたびに変更を追跡します。

これは、Entity Frameworkが自己追跡エンティティ(STE)を実装する方法と似ています。 EF STEでは、追跡対象のエンティティごとにトラッカーオブジェクトが作成され、PropertyChangedのようなイベントをリッスンし、オブジェクトが「ダーティ」かどうかを判断します。

このアプローチの主な利点は、モデルやViewModelで永続状態を保存したり、必要なものを常にDBに保存しなくても、バッチ更新/削除を実行できることです。これにより、懸念(DAL対M対VM対V)の分離がさらに大きくなります。私はMVVMとリポジトリパターンが非常にうまくいっていることがわかります。

ここでは、全体的なアプローチです:

  1. は、あなたがリポジトリ内からデータベースから項目を読み込みます。アイテムをロードするときに、データベースに元々格納されていたオブジェクトのコピーと、「ライブ」(編集可能な)オブジェクトとの関係を保持する「トラッカー」オブジェクトにアイテムを格納します。このプロセスを「チェックポイントの作成」と呼びます。
  2. MVVMで編集可能なオブジェクトを通常どおり使用して、ユーザーが任意の変更を加えることができるようにします。変更を追跡する必要はありません。
  3. ユーザーが[保存]ボタンをクリックすると、画面上のすべてのオブジェクトをリポジトリに戻して保存します。
  4. リポジトリは各オブジェクトを元のコピーと照合し、どのアイテムが「ダーティ」であるかを判断します。
  5. ダーティアイテムのみがデータベースに保存されます。
  6. 保存が成功したら、新しいチェックポイントを作成します。

    まず、ここで私たちは私たちのリポジトリで使用するCarというサンプルクラスがあります:

は、ここで私は手早くいくつかのサンプルコードです。オブジェクトにDirtyプロパティがないことに注目してください。

public class Car : IEquatable<Car>, ICloneable 
{ 
    public string Make { get; set; } 
    public string Model { get; set; } 

    public bool Equals(Car other) 
    { 
     return other.Make == this.Make && 
       other.Model == this.Model; 
    } 

    public object Clone() 
    { 
     return new Car { Make = this.Make, Model = this.Model }; 
    } 
} 

次に、ここでは、あなたのデータベースからオブジェクトを初期化するために使用しますCarRepositoryです:

public class CarRepository 
{ 
    private ChangeTracker<Car> _cars = new ChangeTracker<Car>(); 

    public IEnumerable<Car> GetCars() 
    { 
     //TODO: JET/ADO code here, you would obviously do in a for/while loop 
     int dbId1 = 1; 
     string make1 = "Ford"; 
     string model1 = "Focus"; 

     //TODO: create or update car objects 
     Car car1; 
     if (!_cars.IsTracking(dbId1)) 
      car1 = new Car(); 
     else 
      car1 = _cars.GetItem(dbId1); 

     car1.Make = make1; 
     car1.Model = model1; 

     if (!_cars.IsTracking(dbId1)) 
      _cars.StartTracking(dbId1, car1); 

     return _cars.GetTrackedItems(); 
    } 

    public void SaveCars(IEnumerable<Car> cars) 
    { 
     foreach (var changedItem in _cars.GetChangedItems().Intersect(cars)) 
     { 
      //TODO: JET/ADO code here to update the item 
     } 

     foreach (var newItem in cars.Except(_cars.GetTrackedItems())) 
     { 
      //TODO: JET/ADO code here to add the item to the DB and get its new ID 
      int newId = 5; 
      _cars.StartTracking(newId, newItem); 
     }    

     _cars.SetNewCheckpoint(); 
    } 
} 

最後に、ヘルパークラスは、リポジトリの変更を追跡するために使用していることがあり、設定されたチェックポイントは、ChangeTrackerと呼ばれます。

public class ChangeTracker<T> where T : IEquatable<T>, ICloneable 
{ 
    //item "checkpoints" that are internal to this list 
    private Dictionary<int, T> _originals = new Dictionary<int, T>(); 
    private Dictionary<T, int> _originalIndex = new Dictionary<T, int>(); 

    //the current, live-edited objects 
    private Dictionary<int, T> _copies = new Dictionary<int, T>(); 
    private Dictionary<T, int> _copyIndex = new Dictionary<T, int>(); 

    public bool IsChanged(T copy) 
    { 
     var original = _originals[_copyIndex[copy]]; 
     return original.Equals(copy); 
    } 

    public IEnumerable<T> GetChangedItems() 
    { 
     return _copies.Values.Where(c => IsChanged(c)); 
    } 

    public IEnumerable<T> GetTrackedItems() 
    { 
     return _copies.Values; 
    } 

    public void SetNewCheckpoint() 
    { 
     foreach (var copy in this.GetChangedItems().ToList()) 
     { 
      int dbId = _copyIndex[copy]; 
      var oldOriginal = _originals[dbId]; 
      var newOriginal = (T)copy.Clone(); 

      _originals[dbId] = newOriginal; 
      _originalIndex.Remove(oldOriginal); 
      _originalIndex.Add(newOriginal, dbId); 
     } 
    } 

    public void StartTracking(int dbId, T item) 
    { 
     var newOriginal = (T)item.Clone(); 
     _originals[dbId] = newOriginal; 
     _originalIndex[newOriginal] = dbId; 

     _copies[dbId] = item; 
     _copyIndex[item] = dbId; 
    } 

    public void StopTracking(int dbId) 
    { 
     var original = _originals[dbId]; 
     var copy = _copies[dbId]; 

     _copies.Remove(dbId); 
     _originals.Remove(dbId); 
     _copyIndex.Remove(copy); 
     _originalIndex.Remove(original); 
    } 

    public bool IsTracking(int dbId) 
    { 
     return _originals.ContainsKey(dbId); 
    } 

    public bool IsTracking(T item) 
    { 
     return _copyIndex.ContainsKey(item); 
    } 

    public T GetItem(int dbId) 
    { 
     return _liveCopies[dbId]; 
    } 
} 

そして、ここにあなたがプログラムであなたのリポジトリを使用する方法は次のとおりです。

static void Main(string[] args) 
{ 
    var repository = new CarRepository(); 

    var cars = repository.GetCars().ToArray(); 

    //make some arbitrary changes... 
    cars[0].Make = "Chevy"; 
    cars[1].Model = "Van"; 

    //when we call SaveCars, the repository will detect that 
    //both of these cars have changed, and write them to the database 
    repository.SaveCars(cars); 
} 

これらは確かに必要ではなく、より良い方法がありそうですが、これナイーブな実装は、IEquatableとICloneableに依存しています物事が変わったかどうかを判断するより効率的な方法があるかもしれません。 (たとえば、オブジェクトコピーを作成するというアイデアは、メモリにはあまり適していません)。削除されたアイテムも処理する必要がありますが、上記のサンプルに追加するのは簡単です。

+0

こんにちはKevin、私はこれを半分しか理解していません。実際にそのコードで何が起こっているのか説明できますか?物語のようなもの? –

+0

こんにちはOmar、私はいくつかの編集をしました、私はそれが参考になることを願っています。非常に短いバージョンは、ユーザーが変更を加えたときにダーティーフラグを管理するのではなく、ユーザーが[保存]をクリックしたときに汚れているオブジェクトを計算するだけで、ダーティトラッキングを実行しているということです。 –

+0

ちょっとケビン、ovveriding GetHashCodeについてはどうですか? –

関連する問題