using System; using System.Diagnostics; using System.Runtime.Serialization; namespace Umbraco.Core.Models.Entities { /// /// Provides a base class for entities. /// [Serializable] [DataContract(IsReference = true)] [DebuggerDisplay("Id: {" + nameof(Id) + "}")] public abstract class EntityBase : BeingDirtyBase, IEntity { #if DEBUG_MODEL public Guid InstanceId = Guid.NewGuid(); #endif private bool _hasIdentity; private int _id; private Guid _key; private DateTime _createDate; private DateTime _updateDate; /// [DataMember] public int Id { get => _id; set { SetPropertyValueAndDetectChanges(value, ref _id, nameof(Id)); _hasIdentity = value != 0; } } /// [DataMember] public Guid Key { get { // if an entity does NOT have a key yet, assign one now if (_key == Guid.Empty) _key = Guid.NewGuid(); return _key; } set => SetPropertyValueAndDetectChanges(value, ref _key, nameof(Key)); } /// [DataMember] public DateTime CreateDate { get => _createDate; set => SetPropertyValueAndDetectChanges(value, ref _createDate, nameof(CreateDate)); } /// [DataMember] public DateTime UpdateDate { get => _updateDate; set => SetPropertyValueAndDetectChanges(value, ref _updateDate, nameof(UpdateDate)); } /// [DataMember] public DateTime? DeleteDate { get; set; } // no change tracking - not persisted /// [DataMember] public virtual bool HasIdentity => _hasIdentity; /// /// Resets the entity identity. /// internal virtual void ResetIdentity() { _id = default; _key = Guid.Empty; _hasIdentity = false; } public virtual bool Equals(EntityBase other) { return other != null && (ReferenceEquals(this, other) || SameIdentityAs(other)); } public override bool Equals(object obj) { return obj != null && (ReferenceEquals(this, obj) || SameIdentityAs(obj as EntityBase)); } private bool SameIdentityAs(EntityBase other) { if (other == null) return false; // same identity if // - same object (reference equals) // - or same CLR type, both have identities, and they are identical if (ReferenceEquals(this, other)) return true; return GetType() == other.GetType() && HasIdentity && other.HasIdentity && Id == other.Id; } public override int GetHashCode() { unchecked { var hashCode = HasIdentity.GetHashCode(); hashCode = (hashCode * 397) ^ Id; hashCode = (hashCode * 397) ^ GetType().GetHashCode(); return hashCode; } } public object DeepClone() { // memberwise-clone (ie shallow clone) the entity var unused = Key; // ensure that 'this' has a key, before cloning var clone = (EntityBase) MemberwiseClone(); #if DEBUG_MODEL clone.InstanceId = Guid.NewGuid(); #endif //disable change tracking while we deep clone IDeepCloneable properties clone.DisableChangeTracking(); // deep clone ref properties that are IDeepCloneable DeepCloneHelper.DeepCloneRefProperties(this, clone); PerformDeepClone(clone); // clear changes (ensures the clone has its own dictionaries) clone.ResetDirtyProperties(false); //re-enable change tracking clone.EnableChangeTracking(); return clone; } /// /// Used by inheritors to modify the DeepCloning logic /// /// protected virtual void PerformDeepClone(object clone) { } } }