using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
namespace Umbraco.Core.Models.EntityBase
{
///
/// A base class for use to implement IRememberBeingDirty/ICanBeDirty
///
[Serializable]
[DataContract(IsReference = true)]
public abstract class TracksChangesEntityBase : IRememberBeingDirty
{
//TODO: This needs to go on to ICanBeDirty http://issues.umbraco.org/issue/U4-5662
public virtual IEnumerable GetDirtyProperties()
{
return _propertyChangedInfo == null
? Enumerable.Empty()
: _propertyChangedInfo.Where(x => x.Value).Select(x => x.Key);
}
internal virtual IEnumerable GetPreviouslyDirtyProperties()
{
return _lastPropertyChangedInfo == null
? Enumerable.Empty()
: _lastPropertyChangedInfo.Where(x => x.Value).Select(x => x.Key);
}
private bool _changeTrackingEnabled = true;
///
/// Tracks the properties that have changed
///
private IDictionary _propertyChangedInfo;
///
/// Tracks the properties that we're changed before the last commit (or last call to ResetDirtyProperties)
///
private IDictionary _lastPropertyChangedInfo;
///
/// Property changed event
///
public event PropertyChangedEventHandler PropertyChanged;
///
/// Method to call on a property setter.
///
/// The property info.
protected virtual void OnPropertyChanged(PropertyInfo propertyInfo)
{
//return if we're not tracking changes
if (_changeTrackingEnabled == false) return;
if (_propertyChangedInfo == null)
_propertyChangedInfo = new Dictionary();
_propertyChangedInfo[propertyInfo.Name] = true;
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyInfo.Name));
}
///
/// Indicates whether a specific property on the current entity is dirty.
///
/// Name of the property to check
/// True if Property is dirty, otherwise False
public virtual bool IsPropertyDirty(string propertyName)
{
return _propertyChangedInfo != null && _propertyChangedInfo.Any(x => x.Key == propertyName);
}
///
/// Indicates whether the current entity is dirty.
///
/// True if entity is dirty, otherwise False
public virtual bool IsDirty()
{
return _propertyChangedInfo != null && _propertyChangedInfo.Any();
}
///
/// Indicates that the entity had been changed and the changes were committed
///
///
public virtual bool WasDirty()
{
return _lastPropertyChangedInfo != null && _lastPropertyChangedInfo.Any();
}
///
/// Indicates whether a specific property on the current entity was changed and the changes were committed
///
/// Name of the property to check
/// True if Property was changed, otherwise False. Returns false if the entity had not been previously changed.
public virtual bool WasPropertyDirty(string propertyName)
{
return _lastPropertyChangedInfo != null && _lastPropertyChangedInfo.Any(x => x.Key == propertyName);
}
///
/// Resets the remembered dirty properties from before the last commit
///
public void ForgetPreviouslyDirtyProperties()
{
//NOTE: We cannot .Clear() because when we memberwise clone this will be the SAME
// instance as the one on the clone, so we need to create a new instance.
_lastPropertyChangedInfo = null;
}
///
/// Resets dirty properties by clearing the dictionary used to track changes.
///
///
/// Please note that resetting the dirty properties could potentially
/// obstruct the saving of a new or updated entity.
///
public virtual void ResetDirtyProperties()
{
ResetDirtyProperties(true);
}
///
/// Resets dirty properties by clearing the dictionary used to track changes.
///
///
/// true if we are to remember the last changes made after resetting
///
///
/// Please note that resetting the dirty properties could potentially
/// obstruct the saving of a new or updated entity.
///
public virtual void ResetDirtyProperties(bool rememberPreviouslyChangedProperties)
{
if (rememberPreviouslyChangedProperties)
{
//copy the changed properties to the last changed properties
if (_propertyChangedInfo != null)
{
_lastPropertyChangedInfo = _propertyChangedInfo.ToDictionary(v => v.Key, v => v.Value);
}
}
//NOTE: We cannot .Clear() because when we memberwise clone this will be the SAME
// instance as the one on the clone, so we need to create a new instance.
_propertyChangedInfo = null;
}
public void ResetChangeTrackingCollections()
{
_propertyChangedInfo = null;
_lastPropertyChangedInfo = null;
}
public void DisableChangeTracking()
{
_changeTrackingEnabled = false;
}
public void EnableChangeTracking()
{
_changeTrackingEnabled = true;
}
///
/// Used by inheritors to set the value of properties, this will detect if the property value actually changed and if it did
/// it will ensure that the property has a dirty flag set.
///
///
///
///
/// returns true if the value changed
///
/// This is required because we don't want a property to show up as "dirty" if the value is the same. For example, when we
/// save a document type, nearly all properties are flagged as dirty just because we've 'reset' them, but they are all set
/// to the same value, so it's really not dirty.
///
internal void SetPropertyValueAndDetectChanges(T newVal, ref T origVal, PropertyInfo propertySelector)
{
if ((typeof(T) == typeof(string) == false) && TypeHelper.IsTypeAssignableFrom(typeof(T)))
{
throw new InvalidOperationException("This method does not support IEnumerable instances. For IEnumerable instances a manual custom equality check will be required");
}
SetPropertyValueAndDetectChanges(newVal, ref origVal, propertySelector, EqualityComparer.Default);
}
///
/// Used by inheritors to set the value of properties, this will detect if the property value actually changed and if it did
/// it will ensure that the property has a dirty flag set.
///
///
///
///
/// The equality comparer to use
/// returns true if the value changed
///
/// This is required because we don't want a property to show up as "dirty" if the value is the same. For example, when we
/// save a document type, nearly all properties are flagged as dirty just because we've 'reset' them, but they are all set
/// to the same value, so it's really not dirty.
///
internal void SetPropertyValueAndDetectChanges(T newVal, ref T origVal, PropertyInfo propertySelector, IEqualityComparer comparer)
{
//don't track changes, just set the value
if (_changeTrackingEnabled == false)
{
//set the original value
origVal = newVal;
}
else
{
//check changed
var changed = comparer.Equals(origVal, newVal) == false;
//set the original value
origVal = newVal;
//raise the event if it was changed
if (changed)
{
OnPropertyChanged(propertySelector);
}
}
}
}
}