using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.Serialization; namespace Umbraco.Core.Models.EntityBase { /// /// Base Abstract Entity /// [Serializable] [DataContract(IsReference = true)] [DebuggerDisplay("Id: {Id}")] public abstract class Entity : IEntity, IRememberBeingDirty, ICanBeDirty { private bool _hasIdentity; private int? _hash; private int _id; private Guid _key; /// /// Integer Id /// [DataMember] public int Id { get { return _id; } set { _id = value; HasIdentity = true; } } /// /// Guid based Id /// /// The key is currectly used to store the Unique Id from the /// umbracoNode table, which many of the entities are based on. [DataMember] public Guid Key { get { if (_key == Guid.Empty) return _id.ToGuid(); return _key; } set { _key = value; } } /// /// Gets or sets the Created Date /// [DataMember] public DateTime CreateDate { get; set; } /// /// Gets or sets the Modified Date /// [DataMember] public DateTime UpdateDate { get; set; } /// /// Property changed event /// public event PropertyChangedEventHandler PropertyChanged; /// /// Method to call on a property setter. /// /// The property info. protected virtual void OnPropertyChanged(PropertyInfo propertyInfo) { _propertyChangedInfo[propertyInfo.Name] = true; if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyInfo.Name)); } } internal virtual void ResetIdentity() { _hasIdentity = false; _id = default(int); } /// /// Method to call on entity saved when first added /// internal virtual void AddingEntity() { CreateDate = DateTime.Now; UpdateDate = DateTime.Now; } /// /// Method to call on entity saved/updated /// internal virtual void UpdatingEntity() { UpdateDate = DateTime.Now; } /// /// Tracks the properties that have changed /// private readonly IDictionary _propertyChangedInfo = new Dictionary(); /// /// Tracks the properties that we're changed before the last commit (or last call to ResetDirtyProperties) /// private IDictionary _lastPropertyChangedInfo = null; /// /// 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.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.Any(); } /// /// Indicates that the entity had been changed and the changes were committed /// /// public 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 WasDirty() && _lastPropertyChangedInfo.Any(x => x.Key == propertyName); } /// /// Resets the remembered dirty properties from before the last commit /// public void ForgetPreviouslyDirtyProperties() { _lastPropertyChangedInfo.Clear(); } /// /// 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. /// internal void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) { if (rememberPreviouslyChangedProperties) { //copy the changed properties to the last changed properties _lastPropertyChangedInfo = _propertyChangedInfo.ToDictionary(v => v.Key, v => v.Value); } _propertyChangedInfo.Clear(); } /// /// 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 bool SetPropertyValueAndDetectChanges(Func setValue, T value, PropertyInfo propertySelector) { var initVal = value; var newVal = setValue(value); if (!Equals(initVal, newVal)) { OnPropertyChanged(propertySelector); return true; } return false; } /// /// Indicates whether the current entity has an identity, eg. Id. /// public virtual bool HasIdentity { get { return _hasIdentity; } protected set { _hasIdentity = value; } } public static bool operator ==(Entity left, Entity right) { if (ReferenceEquals(null, left)) return false; return left.Equals(right); } public static bool operator !=(Entity left, Entity right) { return !(left == right); } public virtual bool SameIdentityAs(IEntity other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; return SameIdentityAs(other as Entity); } public virtual bool Equals(Entity other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; return SameIdentityAs(other); } public virtual Type GetRealType() { return GetType(); } public virtual bool SameIdentityAs(Entity other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; if (GetType() == other.GetRealType() && HasIdentity && other.HasIdentity) return other.Id.Equals(Id); return false; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; return SameIdentityAs(obj as IEntity); } public override int GetHashCode() { if (!_hash.HasValue) _hash = !HasIdentity ? new int?(base.GetHashCode()) : new int?(Id.GetHashCode() * 397 ^ GetType().GetHashCode()); return _hash.Value; } } }