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
}
2018-02-13 00:15:04 +11:00
internal virtual IEnumerable < string > GetPreviouslyDirtyProperties ()
2018-02-07 15:38:50 +01:00
{
return _lastPropertyChangedInfo == null
? Enumerable . Empty < string >()
: _lastPropertyChangedInfo . 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 17:14:13 +02:00
if ( PropertyChanged != null )
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 )
{
2016-06-21 18:11:03 +02:00
return _lastPropertyChangedInfo != null && _lastPropertyChangedInfo . Any ( x => x . Key == propertyName );
2013-03-21 00:05:56 +06:00
}
/// <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
2016-06-21 18:11:03 +02:00
if ( _propertyChangedInfo != null )
{
_lastPropertyChangedInfo = _propertyChangedInfo . ToDictionary ( v => v . Key , v => v . Value );
}
2013-03-21 00:05:56 +06:00
}
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>
2016-06-21 18:11:03 +02:00
/// <param name="newVal"></param>
/// <param name="origVal"></param>
2013-03-21 00:05:56 +06:00
/// <param name="propertySelector"></param>
/// <returns>returns true if the value changed</returns>
/// <remarks>
2016-05-26 17:14:13 +02:00
/// 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
2013-03-21 00:05:56 +06:00
/// to the same value, so it's really not dirty.
/// </remarks>
2016-06-21 18:20:55 +02:00
internal void SetPropertyValueAndDetectChanges < T >( T newVal , ref T origVal , PropertyInfo propertySelector )
2015-03-05 17:08:58 +11:00
{
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" );
}
2016-06-21 18:20:55 +02:00
SetPropertyValueAndDetectChanges ( newVal , ref origVal , propertySelector , EqualityComparer < T >. Default );
2015-03-05 17:08:58 +11: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>
2016-06-21 18:11:03 +02:00
/// <param name="newVal"></param>
/// <param name="origVal"></param>
2015-03-05 17:08:58 +11:00
/// <param name="propertySelector"></param>
/// <param name="comparer">The equality comparer to use</param>
/// <returns>returns true if the value changed</returns>
/// <remarks>
2016-05-26 17:14:13 +02:00
/// 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
2015-03-05 17:08:58 +11:00
/// to the same value, so it's really not dirty.
/// </remarks>
2016-06-21 18:20:55 +02:00
internal void SetPropertyValueAndDetectChanges < T >( T newVal , ref T origVal , PropertyInfo propertySelector , IEqualityComparer < T > comparer )
2013-03-21 00:05:56 +06:00
{
2016-06-21 20:43:02 +02:00
//don't track changes, just set the value
if ( _changeTrackingEnabled == false )
2016-06-21 18:11:03 +02:00
{
2016-06-21 20:43:02 +02:00
//set the original value
2016-06-21 18:20:55 +02:00
origVal = newVal ;
2016-06-21 20:43:02 +02:00
}
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 );
}
2016-06-21 18:11:03 +02:00
}
2013-03-21 00:05:56 +06:00
}
}
2018-02-07 15:38:50 +01:00
}