Поскольку вы используете кнопку «Обновить / Сохранить» для фиксации изменений в базе данных, я бы рекомендовал использовать шаблон, похожий на репозиторий, где репозиторий отслеживает изменения всякий раз, когда он выполняет операции сохранения.
Это похоже на то, как Entity Framework реализует объекты самоконтроля (STE). В EF STE для каждой сущности, которую вы хотите отслеживать, создается объект отслеживания, который прослушивает события, подобные PropertyChanged
, чтобы определить, является ли объект «грязным».
Основным преимуществом этого подхода является то, что вы можете выполнять пакетное обновление / удаление без необходимости сохранять какие-либо постоянные состояния с вашими моделями или моделями представления или без необходимости всегда сохранять все, что у вас есть, в БД. Это обеспечивает еще большее разделение интересов (DAL против M против VM против V). Я считаю, что MVVM и шаблон репозитория отлично сочетаются друг с другом.
Вот общий подход:
- Вы загружаете элементы из базы данных из репозитория. Когда вы загружаете элементы, вы сохраняете их в объекте «трекер», который сохраняет копию объекта, как она была изначально сохранена в базе данных, а также связь с «живым» (редактируемым) объектом. Мы называем этот процесс «созданием контрольной точки».
- Вы используете редактируемые объекты в MVVM как обычно, позволяя пользователю вносить любые изменения, которые он хочет. Вам не нужно отслеживать какие-либо изменения.
- Когда пользователь нажимает кнопку «Сохранить», вы отправляете все объекты на экране обратно в хранилище для сохранения.
- Репозиторий проверяет каждый объект на соответствие оригинальным копиям и определяет, какие элементы являются «грязными».
- Только грязные элементы сохраняются в базе данных.
- После успешного сохранения вы создаете новую контрольную точку.
Вот пример кода, который я написал:
Во-первых, вот пример класса 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, хотя они определенно не нужны и, вероятно, существуют более эффективные способы выполнения действий, или у вас может быть более эффективный способ определения того, изменился ли элемент. (Например, идея создания копий объектов не совсем благоприятна для памяти). Вам также придется иметь дело с удаленными элементами, но это было бы легко добавить к примеру выше.