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.


