using System; using System.Diagnostics; using System.Reflection; using System.Runtime.Serialization; namespace Umbraco.Core.Models.EntityBase { /// /// Provides a base class for entities. /// [Serializable] [DataContract(IsReference = true)] [DebuggerDisplay("Id: {" + nameof(Id) + "}")] public abstract class EntityBase : BeingDirtyBase, IEntity { private static readonly Lazy Ps = new Lazy(); private bool _hasIdentity; private int _id; private Guid _key; private DateTime _createDate; private DateTime _updateDate; // ReSharper disable once ClassNeverInstantiated.Local private class PropertySelectors { public readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo(x => x.Id); public readonly PropertyInfo KeySelector = ExpressionHelper.GetPropertyInfo(x => x.Key); public readonly PropertyInfo CreateDateSelector = ExpressionHelper.GetPropertyInfo(x => x.CreateDate); public readonly PropertyInfo UpdateDateSelector = ExpressionHelper.GetPropertyInfo(x => x.UpdateDate); } /// [DataMember] public int Id { get => _id; set { SetPropertyValueAndDetectChanges(value, ref _id, Ps.Value.IdSelector); _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, Ps.Value.KeySelector); } /// [DataMember] public DateTime CreateDate { get => _createDate; set => SetPropertyValueAndDetectChanges(value, ref _createDate, Ps.Value.CreateDateSelector); } /// [DataMember] public DateTime UpdateDate { get => _updateDate; set => SetPropertyValueAndDetectChanges(value, ref _updateDate, Ps.Value.UpdateDateSelector); } /// [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; } /// /// Updates the entity when it is being saved for the first time. /// internal virtual void AddingEntity() { var now = DateTime.Now; // set the create and update dates, if not already set if (IsPropertyDirty("CreateDate") == false || _createDate == default) CreateDate = now; if (IsPropertyDirty("UpdateDate") == false || _updateDate == default) UpdateDate = now; } /// /// Updates the entity when it is being saved. /// internal virtual void UpdatingEntity() { var now = DateTime.Now; // just in case if (_createDate == default) CreateDate = now; // set the update date if not already set if (IsPropertyDirty("UpdateDate") == false || _updateDate == default) UpdateDate = now; } 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 virtual object DeepClone() { // memberwise-clone (ie shallow clone) the entity var unused = Key; // ensure that 'this' has a key, before cloning var clone = (EntityBase) MemberwiseClone(); // clear changes (ensures the clone has its own dictionaries) // then disable change tracking clone.ResetDirtyProperties(false); clone.DisableChangeTracking(); // deep clone ref properties that are IDeepCloneable DeepCloneHelper.DeepCloneRefProperties(this, clone); // clear changes again (just to be sure, because we were not tracking) // then enable change tracking clone.ResetDirtyProperties(false); clone.EnableChangeTracking(); return clone; } } }