189 lines
5.9 KiB
C#
189 lines
5.9 KiB
C#
using System;
|
|
using System.Diagnostics;
|
|
using System.Reflection;
|
|
using System.Runtime.Serialization;
|
|
|
|
namespace Umbraco.Core.Models.Entities
|
|
{
|
|
/// <summary>
|
|
/// Provides a base class for entities.
|
|
/// </summary>
|
|
[Serializable]
|
|
[DataContract(IsReference = true)]
|
|
[DebuggerDisplay("Id: {" + nameof(Id) + "}")]
|
|
public abstract class EntityBase : BeingDirtyBase, IEntity
|
|
{
|
|
#if DEBUG_MODEL
|
|
public Guid InstanceId = Guid.NewGuid();
|
|
#endif
|
|
|
|
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
|
|
|
|
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<EntityBase, int>(x => x.Id);
|
|
public readonly PropertyInfo KeySelector = ExpressionHelper.GetPropertyInfo<EntityBase, Guid>(x => x.Key);
|
|
public readonly PropertyInfo CreateDateSelector = ExpressionHelper.GetPropertyInfo<EntityBase, DateTime>(x => x.CreateDate);
|
|
public readonly PropertyInfo UpdateDateSelector = ExpressionHelper.GetPropertyInfo<EntityBase, DateTime>(x => x.UpdateDate);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
[DataMember]
|
|
public int Id
|
|
{
|
|
get => _id;
|
|
set
|
|
{
|
|
SetPropertyValueAndDetectChanges(value, ref _id, Ps.Value.IdSelector);
|
|
_hasIdentity = value != 0;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
[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);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
[DataMember]
|
|
public DateTime CreateDate
|
|
{
|
|
get => _createDate;
|
|
set => SetPropertyValueAndDetectChanges(value, ref _createDate, Ps.Value.CreateDateSelector);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
[DataMember]
|
|
public DateTime UpdateDate
|
|
{
|
|
get => _updateDate;
|
|
set => SetPropertyValueAndDetectChanges(value, ref _updateDate, Ps.Value.UpdateDateSelector);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
[DataMember]
|
|
public DateTime? DeleteDate { get; set; } // no change tracking - not persisted
|
|
|
|
/// <inheritdoc />
|
|
[DataMember]
|
|
public virtual bool HasIdentity => _hasIdentity;
|
|
|
|
/// <summary>
|
|
/// Resets the entity identity.
|
|
/// </summary>
|
|
internal virtual void ResetIdentity()
|
|
{
|
|
_id = default;
|
|
_key = Guid.Empty;
|
|
_hasIdentity = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the entity when it is being saved for the first time.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the entity when it is being saved.
|
|
/// </summary>
|
|
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();
|
|
|
|
#if DEBUG_MODEL
|
|
clone.InstanceId = Guid.NewGuid();
|
|
#endif
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
}
|