// Copyright (c) Umbraco. // See LICENSE for more details. using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Reflection; using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Extensions; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Models.Collections; public abstract class Item : IEntity, ICanBeDirty { /// /// Tracks the properties that have changed /// private readonly IDictionary _propertyChangedInfo; private bool _hasIdentity; private int _id; private Guid _key; private bool _withChanges = true; // should we track changes? protected Item() => _propertyChangedInfo = new Dictionary(); /// /// Gets or sets a value indicating whether some action against an entity was cancelled through some event. /// This only exists so we have a way to check if an event was cancelled through /// the new api, which also needs to take effect in the legacy api. /// [IgnoreDataMember] internal bool WasCancelled { get; set; } /// /// Property changed event /// public event PropertyChangedEventHandler PropertyChanged; /// /// 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) => _propertyChangedInfo.Any(x => x.Key == propertyName); public virtual IEnumerable GetDirtyProperties() => _propertyChangedInfo.Keys; /// /// Indicates whether the current entity is dirty. /// /// True if entity is dirty, otherwise False public virtual bool IsDirty() => _propertyChangedInfo.Any(); /// /// 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() => _propertyChangedInfo.Clear(); /// /// Disables change tracking. /// public void DisableChangeTracking() => _withChanges = false; /// /// Enables change tracking. /// public void EnableChangeTracking() => _withChanges = true; /// /// Gets or sets the integer Id /// [DataMember] public int Id { get => _id; set { _id = value; HasIdentity = true; } } /// /// Gets or sets the 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 => _key == Guid.Empty ? _id.ToGuid() : _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; } /// /// Gets or sets the Deleted Date /// [DataMember] public DateTime? DeleteDate { get; set; } public virtual void ResetIdentity() { _hasIdentity = false; _id = default; } /// /// Gets or sets a value indicating whether the current entity has an identity, eg. Id. /// public virtual bool HasIdentity { get => _hasIdentity; protected set => _hasIdentity = value; } /*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; }*/ public object DeepClone() => MemberwiseClone(); /// /// Method to call on a property setter. /// /// The property info. protected virtual void OnPropertyChanged(PropertyInfo propertyInfo) { if (_withChanges == false) { return; } _propertyChangedInfo[propertyInfo.Name] = true; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyInfo.Name)); } /// /// 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; public static bool operator ==(Item left, Item right) => ReferenceEquals(left, right); public static bool operator !=(Item left, Item right) => !(left == right); public override bool Equals(object obj) { if (ReferenceEquals(this, obj)) { return true; } if (ReferenceEquals(obj, null)) { return false; } throw new NotImplementedException(); } }