232 lines
9.5 KiB
C#
232 lines
9.5 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// A base class for use to implement IRememberBeingDirty/ICanBeDirty
|
|
/// </summary>
|
|
[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<string> GetDirtyProperties()
|
|
{
|
|
return _propertyChangedInfo == null
|
|
? Enumerable.Empty<string>()
|
|
: _propertyChangedInfo.Where(x => x.Value).Select(x => x.Key);
|
|
}
|
|
|
|
internal virtual IEnumerable<string> GetPreviouslyDirtyProperties()
|
|
{
|
|
return _lastPropertyChangedInfo == null
|
|
? Enumerable.Empty<string>()
|
|
: _lastPropertyChangedInfo.Where(x => x.Value).Select(x => x.Key);
|
|
}
|
|
|
|
private bool _changeTrackingEnabled = true;
|
|
|
|
/// <summary>
|
|
/// Tracks the properties that have changed
|
|
/// </summary>
|
|
private IDictionary<string, bool> _propertyChangedInfo;
|
|
|
|
/// <summary>
|
|
/// Tracks the properties that we're changed before the last commit (or last call to ResetDirtyProperties)
|
|
/// </summary>
|
|
private IDictionary<string, bool> _lastPropertyChangedInfo;
|
|
|
|
/// <summary>
|
|
/// Property changed event
|
|
/// </summary>
|
|
public event PropertyChangedEventHandler PropertyChanged;
|
|
|
|
/// <summary>
|
|
/// Method to call on a property setter.
|
|
/// </summary>
|
|
/// <param name="propertyInfo">The property info.</param>
|
|
protected virtual void OnPropertyChanged(PropertyInfo propertyInfo)
|
|
{
|
|
//return if we're not tracking changes
|
|
if (_changeTrackingEnabled == false) return;
|
|
|
|
if (_propertyChangedInfo == null)
|
|
_propertyChangedInfo = new Dictionary<string, bool>();
|
|
|
|
_propertyChangedInfo[propertyInfo.Name] = true;
|
|
|
|
if (PropertyChanged != null)
|
|
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyInfo.Name));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indicates whether a specific property on the current entity is dirty.
|
|
/// </summary>
|
|
/// <param name="propertyName">Name of the property to check</param>
|
|
/// <returns>True if Property is dirty, otherwise False</returns>
|
|
public virtual bool IsPropertyDirty(string propertyName)
|
|
{
|
|
return _propertyChangedInfo != null && _propertyChangedInfo.Any(x => x.Key == propertyName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indicates whether the current entity is dirty.
|
|
/// </summary>
|
|
/// <returns>True if entity is dirty, otherwise False</returns>
|
|
public virtual bool IsDirty()
|
|
{
|
|
return _propertyChangedInfo != null && _propertyChangedInfo.Any();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indicates that the entity had been changed and the changes were committed
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public virtual bool WasDirty()
|
|
{
|
|
return _lastPropertyChangedInfo != null && _lastPropertyChangedInfo.Any();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indicates whether a specific property on the current entity was changed and the changes were committed
|
|
/// </summary>
|
|
/// <param name="propertyName">Name of the property to check</param>
|
|
/// <returns>True if Property was changed, otherwise False. Returns false if the entity had not been previously changed.</returns>
|
|
public virtual bool WasPropertyDirty(string propertyName)
|
|
{
|
|
return _lastPropertyChangedInfo != null && _lastPropertyChangedInfo.Any(x => x.Key == propertyName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets the remembered dirty properties from before the last commit
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets dirty properties by clearing the dictionary used to track changes.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Please note that resetting the dirty properties could potentially
|
|
/// obstruct the saving of a new or updated entity.
|
|
/// </remarks>
|
|
public virtual void ResetDirtyProperties()
|
|
{
|
|
ResetDirtyProperties(true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets dirty properties by clearing the dictionary used to track changes.
|
|
/// </summary>
|
|
/// <param name="rememberPreviouslyChangedProperties">
|
|
/// true if we are to remember the last changes made after resetting
|
|
/// </param>
|
|
/// <remarks>
|
|
/// Please note that resetting the dirty properties could potentially
|
|
/// obstruct the saving of a new or updated entity.
|
|
/// </remarks>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="newVal"></param>
|
|
/// <param name="origVal"></param>
|
|
/// <param name="propertySelector"></param>
|
|
/// <returns>returns true if the value changed</returns>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
internal void SetPropertyValueAndDetectChanges<T>(T newVal, ref T origVal, PropertyInfo propertySelector)
|
|
{
|
|
if ((typeof(T) == typeof(string) == false) && TypeHelper.IsTypeAssignableFrom<IEnumerable>(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<T>.Default);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="newVal"></param>
|
|
/// <param name="origVal"></param>
|
|
/// <param name="propertySelector"></param>
|
|
/// <param name="comparer">The equality comparer to use</param>
|
|
/// <returns>returns true if the value changed</returns>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
internal void SetPropertyValueAndDetectChanges<T>(T newVal, ref T origVal, PropertyInfo propertySelector, IEqualityComparer<T> 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|