2012-03-14 4 views
5

Đây là một câu hỏi khái niệm hơn. Đây là tình trạng khó khăn hiện tại của tôi; Tôi đang viết một ứng dụng WPF vb.net và sử dụng mẫu MVVM (yêu nó! Khả năng bảo trì chỉ là tuyệt vời). Hiện tại tất cả các mã được viết bằng tay và không sử dụng NHibernate hoặc Entity Framework vì backend là cơ sở dữ liệu truy cập (do chính sách tôi không thể sử dụng NH và EF không hỗ trợ Cơ sở dữ liệu JET, chúng tôi có thể chuyển sang MSSQL tại một số điểm nhưng đó có thể là một lúc kể từ bây giờ).Cách thích hợp để cập nhật các bản ghi trong mẫu MVVM cho hiệu quả tối đa

Ứng dụng đang chạy khá tốt và tự hỏi cách tốt nhất để gửi cập nhật về cơ sở dữ liệu là gì.

Hiện tại, phương pháp là thêm boolean vào bản ghi trên phần thiết lập của mô hình thành "dirty", khi cập nhật được nhấn, chúng tôi lặp qua tất cả các bản ghi "bẩn" và sử dụng oledbcommand (thực thi với tham số) câu lệnh sql để cập nhật. Điều này tạo ra một sự tách biệt tuyệt vời của mối quan tâm nhưng nếu đây là sai cách tôi muốn biết lựa chọn thay thế (xin lưu ý các loại cơ sở dữ liệu và các hạn chế liên quan như vậy là nó không làm việc với EF).

Cảm ơn!

mã cuối cùng trong VB.NET sau ý kiến ​​vv:

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 

Trả lời

3

Vì bạn đang sử dụng một Cập nhật/nút Save để thực hiện bạn thay đổi cơ sở dữ liệu, tôi sẽ khuyên bạn sử dụng một mô hình Repository giống như, nơi Kho lưu trữ thay đổi theo dõi bất cứ khi nào nó thực hiện các thao tác lưu.

Điều này tương tự như cách khung thực thể triển khai thực thể tự theo dõi (STE). Trong EF STE, một đối tượng theo dõi được tạo cho từng thực thể bạn muốn theo dõi mà lắng nghe các sự kiện tương tự như PropertyChanged để xác định xem một đối tượng có 'bẩn' hay không.

Lợi ích chính của phương pháp này là bạn có thể thực hiện cập nhật/xóa hàng loạt mà không cần lưu trữ bất kỳ trạng thái lưu giữ nào với Mô hình hoặc Chế độ xem hoặc phải luôn lưu mọi thứ bạn có vào DB. Điều này cung cấp sự tách biệt lớn hơn các mối quan tâm (DAL vs M vs VM vs V). Tôi thấy rằng MVVM và Pattern Repository đi rất tốt với nhau.

Dưới đây là cách tiếp cận tổng thể:

  1. Bạn mục tải từ cơ sở dữ liệu từ bên trong một Repository. Khi bạn tải các mục, bạn lưu trữ chúng trong một đối tượng "theo dõi" giữ lại một bản sao của đối tượng như ban đầu được lưu trữ trong cơ sở dữ liệu, cũng như mối quan hệ với đối tượng "sống" (có thể chỉnh sửa). Chúng tôi gọi quá trình này là "tạo điểm kiểm tra".
  2. Bạn sử dụng các đối tượng có thể chỉnh sửa trong MVVM như bình thường, cho phép người dùng thực hiện bất kỳ thay đổi nào họ muốn. Bạn không cần theo dõi bất kỳ thay đổi nào.
  3. Khi người dùng nhấp vào nút 'Lưu', bạn gửi tất cả các đối tượng trên màn hình trở lại vào kho lưu trữ để được lưu.
  4. Kho lưu trữ kiểm tra từng đối tượng đối với các bản sao gốc và xác định các mục nào là "bẩn".
  5. Chỉ các mục bẩn được lưu vào cơ sở dữ liệu.
  6. Sau khi lưu thành công, bạn tạo một trạm kiểm soát mới.

Dưới đây là một số mẫu mã Tôi whipped lên:

Thứ nhất, đây là một lớp học mẫu gọi Car mà chúng ta sẽ sử dụng trong Kho lưu trữ của chúng tôi. Lưu ý rằng không có thuộc tính Dirty trên đối tượng.

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 }; 
    } 
} 

Tiếp theo, đây là một CarRepository mà bạn sẽ sử dụng để khởi tạo các đối tượng từ cơ sở dữ liệu của bạn:

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(); 
    } 
} 

Cuối cùng, có một lớp helper Repository sử dụng để theo dõi những thay đổi và thiết lập các điểm kiểm tra gọi là 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]; 
    } 
} 

Và, dưới đây là cách bạn sẽ sử dụng Repository của bạn trong một chương trình:

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); 
} 

thi này ngây thơ dựa vào IEquatable và ICloneable, mặc dù chúng đã chắc chắn không cần thiết và có những cách có thể tốt hơn làm mọi thứ hoặc bạn có thể có một cách hiệu quả hơn để xác định liệu một mục đã thay đổi hay chưa. (Ví dụ: ý tưởng tạo bản sao đối tượng không chính xác với bộ nhớ). Bạn cũng sẽ cần xử lý các mục đã xóa, nhưng điều đó sẽ dễ dàng thêm vào mẫu ở trên.

+0

Xin chào Kevin, tôi chỉ hiểu một nửa điều này - bạn có thể giải thích điều gì đang thực sự xảy ra trong mã đó không? Loại giống như một câu chuyện? –

+0

Xin chào Omar, tôi đã thực hiện một vài chỉnh sửa, tôi hy vọng điều đó sẽ hữu ích. Các phiên bản rất ngắn là chúng tôi vẫn đang làm theo dõi bẩn, ngoại trừ chúng tôi chỉ tính toán đối tượng nào bị bẩn khi người dùng nhấp vào Lưu, thay vì quản lý cờ bẩn khi người dùng thực hiện thay đổi. –

+0

Xin chào Kevin, Còn về GetHashCode trong buồng trứng thì sao? –