192 lines
6.8 KiB
C#
192 lines
6.8 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Linq;
|
|
using System.Runtime.Serialization;
|
|
|
|
namespace Umbraco.Core.Models.Entities
|
|
{
|
|
/// <summary>
|
|
/// Provides a base implementation of <see cref="ICanBeDirty"/> and <see cref="IRememberBeingDirty"/>.
|
|
/// </summary>
|
|
[Serializable]
|
|
[DataContract(IsReference = true)]
|
|
public abstract class BeingDirtyBase : IRememberBeingDirty
|
|
{
|
|
private bool _withChanges = true; // should we track changes?
|
|
private Dictionary<string, bool> _currentChanges; // which properties have changed?
|
|
private Dictionary<string, bool> _savedChanges; // which properties had changed at last commit?
|
|
|
|
#region ICanBeDirty
|
|
|
|
/// <inheritdoc />
|
|
public virtual bool IsDirty()
|
|
{
|
|
return _currentChanges != null && _currentChanges.Any();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public virtual bool IsPropertyDirty(string propertyName)
|
|
{
|
|
return _currentChanges != null && _currentChanges.Any(x => x.Key == propertyName);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public virtual IEnumerable<string> GetDirtyProperties()
|
|
{
|
|
// ReSharper disable once MergeConditionalExpression
|
|
return _currentChanges == null
|
|
? Enumerable.Empty<string>()
|
|
: _currentChanges.Where(x => x.Value).Select(x => x.Key);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
/// <remarks>Saves dirty properties so they can be checked with WasDirty.</remarks>
|
|
public virtual void ResetDirtyProperties()
|
|
{
|
|
ResetDirtyProperties(true);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IRememberBeingDirty
|
|
|
|
/// <inheritdoc />
|
|
public virtual bool WasDirty()
|
|
{
|
|
return _savedChanges != null && _savedChanges.Any();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public virtual bool WasPropertyDirty(string propertyName)
|
|
{
|
|
return _savedChanges != null && _savedChanges.Any(x => x.Key == propertyName);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public virtual void ResetWereDirtyProperties()
|
|
{
|
|
// note: cannot .Clear() because when memberwise-cloning this will be the SAME
|
|
// instance as the one on the clone, so we need to create a new instance.
|
|
_savedChanges = null;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public virtual void ResetDirtyProperties(bool rememberDirty)
|
|
{
|
|
// capture changes if remembering
|
|
// clone the dictionary in case it's shared by an entity clone
|
|
_savedChanges = rememberDirty && _currentChanges != null
|
|
? _currentChanges.ToDictionary(v => v.Key, v => v.Value)
|
|
: null;
|
|
|
|
// note: cannot .Clear() because when memberwise-clone this will be the SAME
|
|
// instance as the one on the clone, so we need to create a new instance.
|
|
_currentChanges = null;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public virtual IEnumerable<string> GetWereDirtyProperties()
|
|
{
|
|
// ReSharper disable once MergeConditionalExpression
|
|
return _savedChanges == null
|
|
? Enumerable.Empty<string>()
|
|
: _savedChanges.Where(x => x.Value).Select(x => x.Key);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Change Tracking
|
|
|
|
/// <summary>
|
|
/// Occurs when a property changes.
|
|
/// </summary>
|
|
public event PropertyChangedEventHandler PropertyChanged;
|
|
|
|
/// <summary>
|
|
/// Registers that a property has changed.
|
|
/// </summary>
|
|
protected virtual void OnPropertyChanged(string propertyName)
|
|
{
|
|
if (_withChanges == false)
|
|
return;
|
|
|
|
if (_currentChanges == null)
|
|
_currentChanges = new Dictionary<string, bool>();
|
|
|
|
_currentChanges[propertyName] = true;
|
|
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disables change tracking.
|
|
/// </summary>
|
|
public void DisableChangeTracking()
|
|
{
|
|
_withChanges = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enables change tracking.
|
|
/// </summary>
|
|
public void EnableChangeTracking()
|
|
{
|
|
_withChanges = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets a property value, detects changes and manages the dirty flag.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of the value.</typeparam>
|
|
/// <param name="value">The new value.</param>
|
|
/// <param name="valueRef">A reference to the value to set.</param>
|
|
/// <param name="propertyName">The property name.</param>
|
|
/// <param name="comparer">A comparer to compare property values.</param>
|
|
protected void SetPropertyValueAndDetectChanges<T>(T value, ref T valueRef, string propertyName, IEqualityComparer<T> comparer = null)
|
|
{
|
|
if (comparer == null)
|
|
{
|
|
// if no comparer is provided, use the default provider, as long as the value is not
|
|
// an IEnumerable - exclude strings, which are IEnumerable but have a default comparer
|
|
var typeofT = typeof(T);
|
|
if (!(typeofT == typeof(string)) && typeof(IEnumerable).IsAssignableFrom(typeofT))
|
|
throw new ArgumentNullException(nameof(comparer), "A custom comparer must be supplied for IEnumerable values.");
|
|
comparer = EqualityComparer<T>.Default;
|
|
}
|
|
|
|
// compare values
|
|
var changed = _withChanges && comparer.Equals(valueRef, value) == false;
|
|
|
|
// assign the new value
|
|
valueRef = value;
|
|
|
|
// handle change
|
|
if (changed)
|
|
OnPropertyChanged(propertyName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Detects changes and manages the dirty flag.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of the value.</typeparam>
|
|
/// <param name="value">The new value.</param>
|
|
/// <param name="orig">The original value.</param>
|
|
/// <param name="propertyName">The property name.</param>
|
|
/// <param name="comparer">A comparer to compare property values.</param>
|
|
/// <param name="changed">A value indicating whether we know values have changed and no comparison is required.</param>
|
|
protected void DetectChanges<T>(T value, T orig, string propertyName, IEqualityComparer<T> comparer, bool changed)
|
|
{
|
|
// compare values
|
|
changed = _withChanges && (changed || !comparer.Equals(orig, value));
|
|
|
|
// handle change
|
|
if (changed)
|
|
OnPropertyChanged(propertyName);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|