2013-03-21 00:05:56 +06:00
using System ;
2015-03-05 17:08:58 +11:00
using System.Collections ;
2013-03-21 00:05:56 +06:00
using System.Collections.Generic ;
using System.ComponentModel ;
using System.Linq ;
using System.Reflection ;
2014-02-20 22:34:54 +11:00
using System.Runtime.Serialization ;
2013-03-21 00:05:56 +06:00
namespace Umbraco.Core.Models.EntityBase
{
/// <summary>
/// A base class for use to implement IRememberBeingDirty/ICanBeDirty
/// </summary>
2014-02-20 22:34:54 +11:00
[Serializable]
[DataContract(IsReference = true)]
2013-03-21 00:05:56 +06:00
public abstract class TracksChangesEntityBase : IRememberBeingDirty
{
2014-10-21 13:12:31 +10:00
//TODO: This needs to go on to ICanBeDirty http://issues.umbraco.org/issue/U4-5662
public virtual IEnumerable < string > GetDirtyProperties ()
{
2016-05-26 16:49:55 +02:00
return _propertyChangedInfo == null
? Enumerable . Empty < string >()
: _propertyChangedInfo . Where ( x => x . Value ). Select ( x => x . Key );
2015-03-03 18:33:04 +11:00
}
private bool _changeTrackingEnabled = true ;
2014-10-21 13:12:31 +10:00
2013-03-21 00:05:56 +06:00
/// <summary>
/// Tracks the properties that have changed
/// </summary>
2016-05-26 16:49:55 +02:00
private IDictionary < string , bool > _propertyChangedInfo ;
2013-03-21 00:05:56 +06:00
/// <summary>
/// Tracks the properties that we're changed before the last commit (or last call to ResetDirtyProperties)
/// </summary>
2016-05-26 16:49:55 +02:00
private IDictionary < string , bool > _lastPropertyChangedInfo ;
2013-03-21 00:05:56 +06:00
/// <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 )
{
2015-03-03 18:33:04 +11:00
//return if we're not tracking changes
if ( _changeTrackingEnabled == false ) return ;
2016-05-26 16:49:55 +02:00
if ( _propertyChangedInfo == null )
_propertyChangedInfo = new Dictionary < string , bool >();
2013-03-21 00:05:56 +06:00
_propertyChangedInfo [ propertyInfo . Name ] = true ;
2016-05-26 16:49:55 +02:00
PropertyChanged ?. Invoke ( this , new PropertyChangedEventArgs ( propertyInfo . Name ));
2013-03-21 00:05:56 +06:00
}
/// <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 )
{
2016-05-26 16:49:55 +02:00
return _propertyChangedInfo != null && _propertyChangedInfo . Any ( x => x . Key == propertyName );
2013-03-21 00:05:56 +06:00
}
/// <summary>
/// Indicates whether the current entity is dirty.
/// </summary>
/// <returns>True if entity is dirty, otherwise False</returns>
public virtual bool IsDirty ()
{
2016-05-26 16:49:55 +02:00
return _propertyChangedInfo != null && _propertyChangedInfo . Any ();
2013-03-21 00:05:56 +06:00
}
/// <summary>
/// Indicates that the entity had been changed and the changes were committed
/// </summary>
/// <returns></returns>
2015-05-07 11:43:53 +10:00
public virtual bool WasDirty ()
2013-03-21 00:05:56 +06:00
{
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 WasDirty () && _lastPropertyChangedInfo . Any ( x => x . Key == propertyName );
}
/// <summary>
/// Resets the remembered dirty properties from before the last commit
/// </summary>
public void ForgetPreviouslyDirtyProperties ()
{
2014-05-09 15:19:00 +10:00
//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.
2016-05-26 16:49:55 +02:00
_lastPropertyChangedInfo = null ;
2013-03-21 00:05:56 +06:00
}
/// <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>
2013-10-10 19:03:59 +11:00
public virtual void ResetDirtyProperties ( bool rememberPreviouslyChangedProperties )
2013-03-21 00:05:56 +06:00
{
if ( rememberPreviouslyChangedProperties )
{
//copy the changed properties to the last changed properties
_lastPropertyChangedInfo = _propertyChangedInfo . ToDictionary ( v => v . Key , v => v . Value );
}
2014-05-09 15:19:00 +10:00
//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.
2016-05-26 16:49:55 +02:00
_propertyChangedInfo = null ;
2013-03-21 00:05:56 +06:00
}
2016-05-26 16:36:53 +02:00
public void ResetChangeTrackingCollections ()
2015-03-03 18:33:04 +11:00
{
2016-05-26 16:49:55 +02:00
_propertyChangedInfo = null ;
_lastPropertyChangedInfo = null ;
2015-03-03 18:33:04 +11:00
}
2016-05-26 16:36:53 +02:00
public void DisableChangeTracking ()
2015-03-03 18:33:04 +11:00
{
_changeTrackingEnabled = false ;
}
2016-05-26 16:36:53 +02:00
public void EnableChangeTracking ()
2015-03-03 18:33:04 +11:00
{
_changeTrackingEnabled = true ;
}
2013-03-21 00:05:56 +06:00
/// <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="setValue"></param>
/// <param name="value"></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>
2015-03-05 17:08:58 +11:00
internal bool SetPropertyValueAndDetectChanges < T >( Func < T , T > setValue , T value , 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" );
}
return SetPropertyValueAndDetectChanges ( setValue , value , propertySelector ,
new DelegateEqualityComparer < T >(
//Standard Equals comparison
( arg1 , arg2 ) => Equals ( arg1 , arg2 ),
arg => arg . GetHashCode ()));
}
/// <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="setValue"></param>
/// <param name="value"></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 bool SetPropertyValueAndDetectChanges < T >( Func < T , T > setValue , T value , PropertyInfo propertySelector , IEqualityComparer < T > comparer )
2013-03-21 00:05:56 +06:00
{
var initVal = value ;
var newVal = setValue ( value );
2014-10-21 13:12:31 +10:00
2015-03-03 18:33:04 +11:00
//don't track changes, just set the value (above)
if ( _changeTrackingEnabled == false ) return false ;
2016-05-26 16:49:55 +02:00
if ( comparer . Equals ( initVal , newVal )) return false ;
OnPropertyChanged ( propertySelector );
return true ;
2013-03-21 00:05:56 +06:00
}
}
}