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