using System; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using System.Text; namespace Umbraco.Core.Models.EntityBase { /// /// Base Abstract Entity /// [Serializable] [DataContract(IsReference = true)] [DebuggerDisplay("Id: {Id}")] public abstract class Entity : TracksChangesEntityBase, IEntity, IRememberBeingDirty, ICanBeDirty { private bool _hasIdentity; private int? _hash; private int _id; private Guid _key; private DateTime _createDate; private DateTime _updateDate; private bool _wasCancelled; private static readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo(x => x.Id); private static readonly PropertyInfo KeySelector = ExpressionHelper.GetPropertyInfo(x => x.Key); private static readonly PropertyInfo CreateDateSelector = ExpressionHelper.GetPropertyInfo(x => x.CreateDate); private static readonly PropertyInfo UpdateDateSelector = ExpressionHelper.GetPropertyInfo(x => x.UpdateDate); private static readonly PropertyInfo HasIdentitySelector = ExpressionHelper.GetPropertyInfo(x => x.HasIdentity); private static readonly PropertyInfo WasCancelledSelector = ExpressionHelper.GetPropertyInfo(x => x.WasCancelled); /// /// Integer Id /// [DataMember] public int Id { get { return _id; } set { SetPropertyValueAndDetectChanges(o => { _id = value; HasIdentity = true; //set the has Identity return _id; }, _id, IdSelector); } } /// /// 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 { SetPropertyValueAndDetectChanges(o => { _key = value; return _key; }, _key, KeySelector); } } /// /// Gets or sets the Created Date /// [DataMember] public DateTime CreateDate { get { return _createDate; } set { SetPropertyValueAndDetectChanges(o => { _createDate = value; return _createDate; }, _createDate, CreateDateSelector); } } /// /// /// Gets or sets the WasCancelled flag, which is used to track /// 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 { return _wasCancelled; } set { SetPropertyValueAndDetectChanges(o => { _wasCancelled = value; return _wasCancelled; }, _wasCancelled, WasCancelledSelector); } } /// /// Gets or sets the Modified Date /// [DataMember] public DateTime UpdateDate { get { return _updateDate; } set { SetPropertyValueAndDetectChanges(o => { _updateDate = value; return _updateDate; }, _updateDate, UpdateDateSelector); } } 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; } /// /// Indicates whether the current entity has an identity, eg. Id. /// public virtual bool HasIdentity { get { return _hasIdentity; } protected set { SetPropertyValueAndDetectChanges(o => { _hasIdentity = value; return _hasIdentity; }, _hasIdentity, HasIdentitySelector); } } 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 virtual T DeepClone() where T : IDeepCloneable { //Memberwise clone on Entity will work since it doesn't have any deep elements // for any sub class this will work for standard properties as well that aren't complex object's themselves. var clone = MemberwiseClone(); ((TracksChangesEntityBase)clone).ResetDirtyProperties(true); return (T)clone; //Using data contract serializer - has issues //var s = Serialize(this); //var d = Deserialize(s, this.GetType()); //return d; //Using binary serializer - has issues //using (var memoryStream = new MemoryStream(10)) //{ //IFormatter formatter = new BinaryFormatter(); //formatter.Serialize(memoryStream, this); //memoryStream.Seek(0, SeekOrigin.Begin); //return formatter.Deserialize(memoryStream); //} } // serialize/deserialize with data contracts: //public static string Serialize(object obj) //{ // using (var memoryStream = new MemoryStream()) // using (var reader = new StreamReader(memoryStream)) // { // var serializer = new DataContractSerializer(obj.GetType()); // serializer.WriteObject(memoryStream, obj); // memoryStream.Position = 0; // return reader.ReadToEnd(); // } //} //public static object Deserialize(string xml, Type toType) //{ // using (Stream stream = new MemoryStream()) // { // using (var writer = new StreamWriter(stream, Encoding.UTF8)) // { // writer.Write(xml); // //byte[] data = Encoding.UTF8.GetBytes(xml); // //stream.Write(data, 0, data.Length); // stream.Position = 0; // var deserializer = new DataContractSerializer(toType); // return deserializer.ReadObject(stream); // } // } //} } }