Para los que trabajan con WPF aplicando el patrón MVVM, les dejo una clase que implementé para poder hacer Rollback en el Modelo.
Muchas veces tenemos el modelo bindeado a los controles, y cuando cambiamos las propiedades en el control, automáticamente se actualizan en el objeto.
Para facilitar la labor de restaurar el objeto a su valor inicial, he creado esta pequeña clase, la cual espero os sea útil. Por supuesto.. estoy abierto a cambios, y a opiniones. :)
PropertyChangedNotifier: Clase que ofrece métodos para poder usar el NotifyPropertyChange en las propiedades de forma cómoda.
Código:
/// <summary> /// Clase que aporta métodos para notificación de cambio de las propiedades. /// </summary> public class PropertyChangedNotifier : INotifyPropertyChanged { #region FIELDS /// <summary> /// Indica si ha habido cambios en el modelo. /// </summary> private bool _isChanged = false; #endregion public static string GetPropertyName<TProperty>(Expression<Func<TProperty>> property) { return ((MemberExpression)property.Body).Member.Name; } protected void RaisePropertyChanged([CallerMemberName] string caller = "") { if (PropertyChanged != null) { //Notificamos la propiedad cambiada. PropertyChanged(this, new PropertyChangedEventArgs(caller)); //Indicamos que el modelo ha tenido cambios. _isChanged = true; } } protected void RaisePropertyChanged<TProperty>(Expression<Func<TProperty>> property) { RaisePropertyChanged(((MemberExpression)property.Body).Member.Name); } public event PropertyChangedEventHandler PropertyChanged; }
ModelBase: Clase base desde la que deben heredar los modelos.
Código:
/// <summary> /// Clase base para modelos, que ofrece funcionalidad de actualización de propiedades, y posibilidad de Rollback. /// </summary> public class ModelBase : PropertyChangedNotifier { #region FIELDS private object _rollBack; #endregion #region TRACKING /// <summary> /// Inicia el seguimiento del modelo. /// </summary> public void Tracking() { //Clono el objeto. this._rollBack = Activator.CreateInstance(this.GetType()); this.CloneProperties(this, _rollBack); } /// <summary> /// Restaura el modelo a su posición inicial, dicha posición inicial se habrá establecido al llamar por primera vez al Tracking. /// </summary> public void Rollback() { this.CloneProperties(_rollBack, this); } /// <summary> /// Copia las propiedades del origen al destino. /// </summary> /// <param name="source"></param> /// <param name="target"></param> private void CloneProperties(object source, object target) { source.GetType().GetProperties().Where(obj => obj.Name != "Item").ToList().ForEach(objProperty => { this.CloneProperties(source, target, objProperty); }); } /// <summary> /// Copia la propiedad del elemento source en el objeto target. /// </summary> /// <param name="source">Objeto al que se le desean copiar las propiedades</param> /// <param name="target">Objeto </param> /// <param name="targetProperty"></param> private void CloneProperties(object source, object target, PropertyInfo targetProperty, string data = null) { //Recuperamos el valor de la propiedad del origen. object sourcePropertyValue = source.GetType().GetProperty(targetProperty.Name).GetValue(source); if (sourcePropertyValue != null) { //Comprobamos si la propiedad es primitiva. if (sourcePropertyValue.GetType().IsPrimitive || sourcePropertyValue.GetType().IsArray || sourcePropertyValue.GetType().IsEnum || sourcePropertyValue.GetType().Equals(typeof(string)) || sourcePropertyValue.GetType().Equals(typeof(Guid)) || sourcePropertyValue.GetType().Equals(typeof(DateTime)) || sourcePropertyValue.GetType().Equals(typeof(byte[]))) { dynamic elementRightType = Convert.ChangeType(sourcePropertyValue, sourcePropertyValue.GetType()); //Asignamos el valor al objeto al que deseamos copiar las propiedades (Si no es de solo escritura) if (targetProperty.CanWrite) targetProperty.SetValue(target, elementRightType); } else { object newSourceProperty = this.GenerateObject(source, sourcePropertyValue); if (targetProperty.CanWrite) targetProperty.SetValue(target, newSourceProperty); } } } /// <summary> /// Genera un duplicado de la propiedad enviada al método, del objeto enviado. /// </summary> /// <param name="source">Objeto origen del que se desea copiar la propiedad</param> /// <param name="sourceObjectProperty">Objeto que representa la propiedad del source, y del cual se realizará un clon.</param> /// <returns></returns> private object GenerateObject(object source, object sourceObjectProperty) { if (sourceObjectProperty.GetType().GetInterface("IEnumerable") != null) return this.GenerateObjectCollection(sourceObjectProperty); else return this.GenerateObjectElement(sourceObjectProperty); } /// <summary> /// Genera un duplicado del objeto pasado. /// </summary> /// <param name="originalProperty"></param> /// <returns></returns> private object GenerateObjectElement(object originalProperty) { //Recuperamos el tipo del que es la propiedad. var constructedPropertyType = originalProperty.GetType(); //Creamos una nueva instancia de ese objeto. dynamic newObjectCreated = Activator.CreateInstance(constructedPropertyType); //Recorremos cada propiedad y se la asignamos. originalProperty.GetType().GetProperties().Where(obj => obj.Name != "Item").ToList().ForEach(newObjectProperty => { this.CloneProperties(originalProperty, newObjectCreated, newObjectProperty); }); return newObjectCreated; } /// <summary> /// Genera un duplicado de un ObservableCollection /// </summary> /// <param name="originalCollection"></param> /// <param name="nameProperty"></param> /// <returns></returns> private object GenerateObjectCollection(object originalCollection) { //Recuperamos el tipo del que es la collección. var constructedTypeList = originalCollection.GetType(); //Recuperamos el tipo de elementos que espera recibir el método Add MethodInfo methodAdd = constructedTypeList.GetMethod("Add"); Type typeElements = methodAdd.GetParameters()[0].ParameterType; //Creamos el nuevo ObservableCollection. System.Collections.IList newObservableCollection = (System.Collections.IList)Activator.CreateInstance(constructedTypeList); //Volcamos los elementos de la lista Original, al nuevo ObservableCollection. System.Collections.IEnumerable enumerableOriginalList = (System.Collections.IEnumerable)originalCollection; foreach (var element in enumerableOriginalList) { //Detectamos si el elemento es un elemento primario. if (element.GetType().IsPrimitive || element.GetType().IsArray || element.GetType().IsEnum || element.GetType().Equals(typeof(string)) || element.GetType().Equals(typeof(Guid)) || element.GetType().Equals(typeof(DateTime)) || element.GetType().Equals(typeof(byte[]))) { //Realizamos la conversión dinámica. dynamic elementCorrectType = Convert.ChangeType(element, element.GetType()); //Agregamos el elemento. newObservableCollection.Add(elementCorrectType); } else { //Creamos una nueva instancia del elemento. object newElement = Activator.CreateInstance(element.GetType()); //Copiamos las propiedes this.CloneProperties(element, newElement); //Realizamos la conversión dinámica. dynamic convertedElement = Convert.ChangeType(newElement, element.GetType()); //Agregamos el elemento. newObservableCollection.Add(convertedElement); } } //Devolvemos la nueva ObservableCollection. return newObservableCollection; } #endregion }
De tal forma, que cuando se quiera controlar el Tracking valdría con lo siguiente:
Código:
PersonModel Model = new PersonModel(); //Se realiza una copia, para luego poder restaurarla. Model.Tracking(); //Después.. cuando se quiera cancelar los cambios, y restaurar el objeto Model.Rollback();
Espero que os sirva. Un saludo.