DataType refactoring preparation - Entity refactoring
This commit is contained in:
37
src/Umbraco.Core/Models/Entities/BeingDirty.cs
Normal file
37
src/Umbraco.Core/Models/Entities/BeingDirty.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Umbraco.Core.Models.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a concrete implementation of <see cref="BeingDirtyBase"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This class is provided for classes that cannot inherit from <see cref="BeingDirtyBase"/>
|
||||
/// and therefore need to implement <see cref="IRememberBeingDirty"/>, by re-using some of
|
||||
/// <see cref="BeingDirtyBase"/> logic.</para>
|
||||
/// </remarks>
|
||||
public sealed class BeingDirty : BeingDirtyBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets a property value, detects changes and manages the dirty flag.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value.</typeparam>
|
||||
/// <param name="value">The new value.</param>
|
||||
/// <param name="valueRef">A reference to the value to set.</param>
|
||||
/// <param name="propertySelector">The property selector.</param>
|
||||
/// <param name="comparer">A comparer to compare property values.</param>
|
||||
public new void SetPropertyValueAndDetectChanges<T>(T value, ref T valueRef, PropertyInfo propertySelector, IEqualityComparer<T> comparer = null)
|
||||
{
|
||||
base.SetPropertyValueAndDetectChanges(value, ref valueRef, propertySelector, comparer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers that a property has changed.
|
||||
/// </summary>
|
||||
public new void OnPropertyChanged(PropertyInfo propertySelector)
|
||||
{
|
||||
base.OnPropertyChanged(propertySelector);
|
||||
}
|
||||
}
|
||||
}
|
||||
183
src/Umbraco.Core/Models/Entities/BeingDirtyBase.cs
Normal file
183
src/Umbraco.Core/Models/Entities/BeingDirtyBase.cs
Normal file
@@ -0,0 +1,183 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Core.Models.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a base implementation of <see cref="ICanBeDirty"/> and <see cref="IRememberBeingDirty"/>.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[DataContract(IsReference = true)]
|
||||
public abstract class BeingDirtyBase : IRememberBeingDirty
|
||||
{
|
||||
private bool _withChanges = true; // should we track changes?
|
||||
private Dictionary<string, bool> _currentChanges; // which properties have changed?
|
||||
private Dictionary<string, bool> _savedChanges; // which properties had changed at last commit?
|
||||
|
||||
#region ICanBeDirty
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual bool IsDirty()
|
||||
{
|
||||
return _currentChanges != null && _currentChanges.Any();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual bool IsPropertyDirty(string propertyName)
|
||||
{
|
||||
return _currentChanges != null && _currentChanges.Any(x => x.Key == propertyName);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<string> GetDirtyProperties()
|
||||
{
|
||||
// ReSharper disable once MergeConditionalExpression
|
||||
return _currentChanges == null
|
||||
? Enumerable.Empty<string>()
|
||||
: _currentChanges.Where(x => x.Value).Select(x => x.Key);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>Saves dirty properties so they can be checked with WasDirty.</remarks>
|
||||
public virtual void ResetDirtyProperties()
|
||||
{
|
||||
ResetDirtyProperties(true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IRememberBeingDirty
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual bool WasDirty()
|
||||
{
|
||||
return _savedChanges != null && _savedChanges.Any();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual bool WasPropertyDirty(string propertyName)
|
||||
{
|
||||
return _savedChanges != null && _savedChanges.Any(x => x.Key == propertyName);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ResetWereDirtyProperties()
|
||||
{
|
||||
// note: cannot .Clear() because when memberwise-cloning this will be the SAME
|
||||
// instance as the one on the clone, so we need to create a new instance.
|
||||
_savedChanges = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void ResetDirtyProperties(bool rememberDirty)
|
||||
{
|
||||
// capture changes if remembering
|
||||
// clone the dictionary in case it's shared by an entity clone
|
||||
_savedChanges = rememberDirty && _currentChanges != null
|
||||
? _currentChanges.ToDictionary(v => v.Key, v => v.Value)
|
||||
: null;
|
||||
|
||||
// note: cannot .Clear() because when memberwise-clone this will be the SAME
|
||||
// instance as the one on the clone, so we need to create a new instance.
|
||||
_currentChanges = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Change Tracking
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a property changes.
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Registers that a property has changed.
|
||||
/// </summary>
|
||||
protected virtual void OnPropertyChanged(PropertyInfo propertyInfo)
|
||||
{
|
||||
if (_withChanges == false)
|
||||
return;
|
||||
|
||||
if (_currentChanges == null)
|
||||
_currentChanges = new Dictionary<string, bool>();
|
||||
|
||||
_currentChanges[propertyInfo.Name] = true;
|
||||
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyInfo.Name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disables change tracking.
|
||||
/// </summary>
|
||||
public void DisableChangeTracking()
|
||||
{
|
||||
_withChanges = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables change tracking.
|
||||
/// </summary>
|
||||
public void EnableChangeTracking()
|
||||
{
|
||||
_withChanges = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a property value, detects changes and manages the dirty flag.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value.</typeparam>
|
||||
/// <param name="value">The new value.</param>
|
||||
/// <param name="valueRef">A reference to the value to set.</param>
|
||||
/// <param name="propertySelector">The property selector.</param>
|
||||
/// <param name="comparer">A comparer to compare property values.</param>
|
||||
protected void SetPropertyValueAndDetectChanges<T>(T value, ref T valueRef, PropertyInfo propertySelector, IEqualityComparer<T> comparer = null)
|
||||
{
|
||||
if (comparer == null)
|
||||
{
|
||||
// if no comparer is provided, use the default provider, as long as the value is not
|
||||
// an IEnumerable - exclude strings, which are IEnumerable but have a default comparer
|
||||
var typeofT = typeof(T);
|
||||
if (!(typeofT == typeof(string)) && typeof(IEnumerable).IsAssignableFrom(typeofT))
|
||||
throw new ArgumentNullException(nameof(comparer), "A custom comparer must be supplied for IEnumerable values.");
|
||||
comparer = EqualityComparer<T>.Default;
|
||||
}
|
||||
|
||||
// compare values
|
||||
var changed = _withChanges && comparer.Equals(valueRef, value) == false;
|
||||
|
||||
// assign the new value
|
||||
valueRef = value;
|
||||
|
||||
// handle change
|
||||
if (changed)
|
||||
OnPropertyChanged(propertySelector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detects changes and manages the dirty flag.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value.</typeparam>
|
||||
/// <param name="value">The new value.</param>
|
||||
/// <param name="orig">The original value.</param>
|
||||
/// <param name="propertySelector">The property selector.</param>
|
||||
/// <param name="comparer">A comparer to compare property values.</param>
|
||||
/// <param name="changed">A value indicating whether we know values have changed and no comparison is required.</param>
|
||||
protected void DetectChanges<T>(T value, T orig, PropertyInfo propertySelector, IEqualityComparer<T> comparer, bool changed)
|
||||
{
|
||||
// compare values
|
||||
changed = _withChanges && (changed || !comparer.Equals(orig, value));
|
||||
|
||||
// handle change
|
||||
if (changed)
|
||||
OnPropertyChanged(propertySelector);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
17
src/Umbraco.Core/Models/Entities/ContentEntitySlim.cs
Normal file
17
src/Umbraco.Core/Models/Entities/ContentEntitySlim.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace Umbraco.Core.Models.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements <see cref="IContentEntitySlim"/>.
|
||||
/// </summary>
|
||||
public class ContentEntitySlim : EntitySlim, IContentEntitySlim
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string ContentTypeAlias { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ContentTypeIcon { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ContentTypeThumbnail { get; set; }
|
||||
}
|
||||
}
|
||||
14
src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs
Normal file
14
src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace Umbraco.Core.Models.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements <see cref="IDocumentEntitySlim"/>.
|
||||
/// </summary>
|
||||
public class DocumentEntitySlim : ContentEntitySlim, IDocumentEntitySlim
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool Published { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Edited { get; set; }
|
||||
}
|
||||
}
|
||||
180
src/Umbraco.Core/Models/Entities/EntityBase.cs
Normal file
180
src/Umbraco.Core/Models/Entities/EntityBase.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
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
|
||||
{
|
||||
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();
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
220
src/Umbraco.Core/Models/Entities/EntitySlim.cs
Normal file
220
src/Umbraco.Core/Models/Entities/EntitySlim.cs
Normal file
@@ -0,0 +1,220 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Exceptions;
|
||||
|
||||
namespace Umbraco.Core.Models.Entities
|
||||
{
|
||||
// fixme - changing the name of some properties that were in additionalData => must update corresponding javascript?
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="IEntitySlim"/> for internal use.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Although it implements <see cref="IEntitySlim"/>, this class does not
|
||||
/// implement <see cref="IRememberBeingDirty"/> and everything this interface defines, throws.</para>
|
||||
/// <para>Although it implements <see cref="IEntitySlim"/>, this class does not
|
||||
/// implement <see cref="IDeepCloneable"/> and deep-cloning throws.</para>
|
||||
/// </remarks>
|
||||
public class EntitySlim : IEntitySlim
|
||||
{
|
||||
private IDictionary<string, object> _additionalData;
|
||||
|
||||
/// <summary>
|
||||
/// Gets an entity representing "root".
|
||||
/// </summary>
|
||||
public static readonly IEntitySlim Root = new EntitySlim { Path = "-1", Name = "root", HasChildren = true };
|
||||
|
||||
|
||||
// implement IEntity
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public Guid Key { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public DateTime CreateDate { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public DateTime UpdateDate { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public DateTime? DeleteDate { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public bool HasIdentity => Id != 0;
|
||||
|
||||
|
||||
// implement ITreeEntity
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public int CreatorId { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public int ParentId { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetParent(ITreeEntity parent) => throw new WontImplementException();
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public int Level { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public string Path { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public bool Trashed { get; set; }
|
||||
|
||||
|
||||
// implement IUmbracoEntity
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public IDictionary<string, object> AdditionalData => _additionalData ?? (_additionalData = new Dictionary<string, object>());
|
||||
|
||||
/// <inheritdoc />
|
||||
[IgnoreDataMember]
|
||||
public bool HasAdditionalData => _additionalData != null;
|
||||
|
||||
|
||||
// implement IEntitySlim
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public Guid NodeObjectType { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public bool HasChildren { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public virtual bool IsContainer { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Represents a lightweight property.
|
||||
/// </summary>
|
||||
public class PropertySlim
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PropertySlim"/> class.
|
||||
/// </summary>
|
||||
public PropertySlim(string editorAlias, object value)
|
||||
{
|
||||
PropertyEditorAlias = editorAlias;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property editor alias.
|
||||
/// </summary>
|
||||
public string PropertyEditorAlias { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property value.
|
||||
/// </summary>
|
||||
public object Value { get; }
|
||||
|
||||
protected bool Equals(PropertySlim other)
|
||||
{
|
||||
return PropertyEditorAlias.Equals(other.PropertyEditorAlias) && Equals(Value, other.Value);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != GetType()) return false;
|
||||
return Equals((PropertySlim) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (PropertyEditorAlias.GetHashCode() * 397) ^ (Value != null ? Value.GetHashCode() : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region IDeepCloneable
|
||||
|
||||
/// <inheritdoc />
|
||||
public object DeepClone()
|
||||
{
|
||||
throw new WontImplementException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IRememberBeingDirty
|
||||
|
||||
// IEntitySlim does *not* track changes, but since it indirectly implements IUmbracoEntity,
|
||||
// and therefore IRememberBeingDirty, we have to have those methods - which all throw.
|
||||
|
||||
public bool IsDirty()
|
||||
{
|
||||
throw new WontImplementException();
|
||||
}
|
||||
|
||||
public bool IsPropertyDirty(string propName)
|
||||
{
|
||||
throw new WontImplementException();
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetDirtyProperties()
|
||||
{
|
||||
throw new WontImplementException();
|
||||
}
|
||||
|
||||
public void ResetDirtyProperties()
|
||||
{
|
||||
throw new WontImplementException();
|
||||
}
|
||||
|
||||
public bool WasDirty()
|
||||
{
|
||||
throw new WontImplementException();
|
||||
}
|
||||
|
||||
public bool WasPropertyDirty(string propertyName)
|
||||
{
|
||||
throw new WontImplementException();
|
||||
}
|
||||
|
||||
public void ResetWereDirtyProperties()
|
||||
{
|
||||
throw new WontImplementException();
|
||||
}
|
||||
|
||||
public void ResetDirtyProperties(bool rememberDirty)
|
||||
{
|
||||
throw new WontImplementException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
30
src/Umbraco.Core/Models/Entities/ICanBeDirty.cs
Normal file
30
src/Umbraco.Core/Models/Entities/ICanBeDirty.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Models.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines an entity that tracks property changes and can be dirty.
|
||||
/// </summary>
|
||||
public interface ICanBeDirty
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether the current entity is dirty.
|
||||
/// </summary>
|
||||
bool IsDirty();
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a specific property is dirty.
|
||||
/// </summary>
|
||||
bool IsPropertyDirty(string propName);
|
||||
|
||||
/// <summary>
|
||||
/// Gets properties that are dirty.
|
||||
/// </summary>
|
||||
IEnumerable<string> GetDirtyProperties();
|
||||
|
||||
/// <summary>
|
||||
/// Resets dirty properties.
|
||||
/// </summary>
|
||||
void ResetDirtyProperties();
|
||||
}
|
||||
}
|
||||
23
src/Umbraco.Core/Models/Entities/IContentEntitySlim.cs
Normal file
23
src/Umbraco.Core/Models/Entities/IContentEntitySlim.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace Umbraco.Core.Models.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a lightweight content entity, managed by the entity service.
|
||||
/// </summary>
|
||||
public interface IContentEntitySlim : IEntitySlim
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the content type alias.
|
||||
/// </summary>
|
||||
string ContentTypeAlias { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content type icon.
|
||||
/// </summary>
|
||||
string ContentTypeIcon { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content type thumbnail.
|
||||
/// </summary>
|
||||
string ContentTypeThumbnail { get; }
|
||||
}
|
||||
}
|
||||
18
src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs
Normal file
18
src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace Umbraco.Core.Models.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a lightweight document entity, managed by the entity service.
|
||||
/// </summary>
|
||||
public interface IDocumentEntitySlim : IContentEntitySlim
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the document is published.
|
||||
/// </summary>
|
||||
bool Published { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the document has edited properties.
|
||||
/// </summary>
|
||||
bool Edited { get; }
|
||||
}
|
||||
}
|
||||
45
src/Umbraco.Core/Models/Entities/IEntity.cs
Normal file
45
src/Umbraco.Core/Models/Entities/IEntity.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Models.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines an entity.
|
||||
/// </summary>
|
||||
public interface IEntity : IDeepCloneable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the integer identifier of the entity.
|
||||
/// </summary>
|
||||
int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Guid unique identifier of the entity.
|
||||
/// </summary>
|
||||
Guid Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the creation date.
|
||||
/// </summary>
|
||||
DateTime CreateDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the last update date.
|
||||
/// </summary>
|
||||
DateTime UpdateDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the delete date.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The delete date is null when the entity has not been deleted.</para>
|
||||
/// <para>The delete date has a value when the entity instance has been deleted, but this value
|
||||
/// is transient and not persisted in database (since the entity does not exist anymore).</para>
|
||||
/// </remarks>
|
||||
DateTime? DeleteDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the entity has an identity.
|
||||
/// </summary>
|
||||
bool HasIdentity { get; }
|
||||
}
|
||||
}
|
||||
25
src/Umbraco.Core/Models/Entities/IEntitySlim.cs
Normal file
25
src/Umbraco.Core/Models/Entities/IEntitySlim.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Models.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a lightweight entity, managed by the entity service.
|
||||
/// </summary>
|
||||
public interface IEntitySlim : IUmbracoEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the entity object type.
|
||||
/// </summary>
|
||||
Guid NodeObjectType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the entity has children.
|
||||
/// </summary>
|
||||
bool HasChildren { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the entity is a container.
|
||||
/// </summary>
|
||||
bool IsContainer { get; }
|
||||
}
|
||||
}
|
||||
33
src/Umbraco.Core/Models/Entities/IRememberBeingDirty.cs
Normal file
33
src/Umbraco.Core/Models/Entities/IRememberBeingDirty.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
namespace Umbraco.Core.Models.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines an entity that tracks property changes and can be dirty, and remembers
|
||||
/// which properties were dirty when the changes were committed.
|
||||
/// </summary>
|
||||
public interface IRememberBeingDirty : ICanBeDirty
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether the current entity is dirty.
|
||||
/// </summary>
|
||||
/// <remarks>A property was dirty if it had been changed and the changes were committed.</remarks>
|
||||
bool WasDirty();
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a specific property was dirty.
|
||||
/// </summary>
|
||||
/// <remarks>A property was dirty if it had been changed and the changes were committed.</remarks>
|
||||
bool WasPropertyDirty(string propertyName);
|
||||
|
||||
/// <summary>
|
||||
/// Resets properties that were dirty.
|
||||
/// </summary>
|
||||
void ResetWereDirtyProperties();
|
||||
|
||||
/// <summary>
|
||||
/// Resets dirty properties.
|
||||
/// </summary>
|
||||
/// <param name="rememberDirty">A value indicating whether to remember dirty properties.</param>
|
||||
/// <remarks>When <paramref name="rememberDirty"/> is true, dirty properties are saved so they can be checked with WasDirty.</remarks>
|
||||
void ResetDirtyProperties(bool rememberDirty);
|
||||
}
|
||||
}
|
||||
56
src/Umbraco.Core/Models/Entities/ITreeEntity.cs
Normal file
56
src/Umbraco.Core/Models/Entities/ITreeEntity.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
namespace Umbraco.Core.Models.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines an entity that belongs to a tree.
|
||||
/// </summary>
|
||||
public interface ITreeEntity : IEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the entity.
|
||||
/// </summary>
|
||||
string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier of the user who created this entity.
|
||||
/// </summary>
|
||||
int CreatorId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier of the parent entity.
|
||||
/// </summary>
|
||||
int ParentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the parent entity.
|
||||
/// </summary>
|
||||
/// <remarks>Use this method to set the parent entity when the parent entity is known, but has not
|
||||
/// been persistent and does not yet have an identity. The parent identifier will we retrieved
|
||||
/// from the parent entity when needed. If the parent entity still does not have an entity by that
|
||||
/// time, an exception will be thrown by <see cref="ParentId"/> getter.</remarks>
|
||||
void SetParent(ITreeEntity parent);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the level of the entity.
|
||||
/// </summary>
|
||||
int Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the path to the entity.
|
||||
/// </summary>
|
||||
string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sort order of the entity.
|
||||
/// </summary>
|
||||
int SortOrder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this entity is trashed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Trashed entities are located in the recycle bin.</para>
|
||||
/// <para>Always false for entities that do not support being trashed.</para>
|
||||
/// </remarks>
|
||||
bool Trashed { get; }
|
||||
}
|
||||
}
|
||||
29
src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs
Normal file
29
src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Models.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an entity that can be managed by the entity service.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>An IUmbracoEntity can be related to another via the IRelationService.</para>
|
||||
/// <para>IUmbracoEntities can be retrieved with the IEntityService.</para>
|
||||
/// <para>An IUmbracoEntity can participate in notifications.</para>
|
||||
/// </remarks>
|
||||
public interface IUmbracoEntity : ITreeEntity, IRememberBeingDirty
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets additional data for this entity.
|
||||
/// </summary>
|
||||
/// <remarks>Can be empty, but never null. To avoid allocating, do not
|
||||
/// test for emptyness, but use <see cref="HasAdditionalData"/> instead.</remarks>
|
||||
IDictionary<string, object> AdditionalData { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this entity has additional data.
|
||||
/// </summary>
|
||||
/// <remarks>Use this property to check for additional data without
|
||||
/// getting <see cref="AdditionalData"/>, to avoid allocating.</remarks>
|
||||
bool HasAdditionalData { get; }
|
||||
}
|
||||
}
|
||||
11
src/Umbraco.Core/Models/Entities/IValueObject.cs
Normal file
11
src/Umbraco.Core/Models/Entities/IValueObject.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Umbraco.Core.Models.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Marker interface for value object, eg. objects without
|
||||
/// the same kind of identity as an Entity (with its Id).
|
||||
/// </summary>
|
||||
public interface IValueObject
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
124
src/Umbraco.Core/Models/Entities/TreeEntityBase.cs
Normal file
124
src/Umbraco.Core/Models/Entities/TreeEntityBase.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Core.Models.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a base class for tree entities.
|
||||
/// </summary>
|
||||
public abstract class TreeEntityBase : EntityBase, ITreeEntity
|
||||
{
|
||||
private static PropertySelectors _selectors;
|
||||
private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors());
|
||||
|
||||
private string _name;
|
||||
private int _creatorId;
|
||||
private int _parentId;
|
||||
private bool _hasParentId;
|
||||
private ITreeEntity _parent;
|
||||
private int _level;
|
||||
private string _path;
|
||||
private int _sortOrder;
|
||||
private bool _trashed;
|
||||
|
||||
private class PropertySelectors
|
||||
{
|
||||
public readonly PropertyInfo Name = ExpressionHelper.GetPropertyInfo<ContentBase, string>(x => x.Name);
|
||||
public readonly PropertyInfo CreatorId = ExpressionHelper.GetPropertyInfo<ContentBase, int>(x => x.CreatorId);
|
||||
public readonly PropertyInfo ParentId = ExpressionHelper.GetPropertyInfo<ContentBase, int>(x => x.ParentId);
|
||||
public readonly PropertyInfo Level = ExpressionHelper.GetPropertyInfo<ContentBase, int>(x => x.Level);
|
||||
public readonly PropertyInfo Path = ExpressionHelper.GetPropertyInfo<ContentBase, string>(x => x.Path);
|
||||
public readonly PropertyInfo SortOrder = ExpressionHelper.GetPropertyInfo<ContentBase, int>(x => x.SortOrder);
|
||||
public readonly PropertyInfo Trashed = ExpressionHelper.GetPropertyInfo<ContentBase, bool>(x => x.Trashed);
|
||||
}
|
||||
|
||||
// fixme
|
||||
// ParentId, Path, Level and Trashed all should be consistent, and all derive from parentId, really
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _name, Selectors.Name);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public int CreatorId
|
||||
{
|
||||
get => _creatorId;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _creatorId, Selectors.CreatorId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public int ParentId
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_hasParentId) return _parentId;
|
||||
|
||||
if (_parent == null) throw new InvalidOperationException("Content does not have a parent.");
|
||||
if (!_parent.HasIdentity) throw new InvalidOperationException("Content's parent does not have an identity.");
|
||||
|
||||
_parentId = _parent.Id;
|
||||
if (_parentId == 0)
|
||||
throw new Exception("Panic: parent has an identity but id is zero.");
|
||||
|
||||
_hasParentId = true;
|
||||
_parent = null;
|
||||
return _parentId;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == 0)
|
||||
throw new ArgumentException("Value cannot be zero.", nameof(value));
|
||||
SetPropertyValueAndDetectChanges(value, ref _parentId, Selectors.ParentId);
|
||||
_hasParentId = true;
|
||||
_parent = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetParent(ITreeEntity parent)
|
||||
{
|
||||
_hasParentId = false;
|
||||
_parent = parent;
|
||||
OnPropertyChanged(Selectors.ParentId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public int Level
|
||||
{
|
||||
get => _level;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _level, Selectors.Level);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public string Path
|
||||
{
|
||||
get => _path;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _path, Selectors.Path);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public int SortOrder
|
||||
{
|
||||
get => _sortOrder;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, Selectors.SortOrder);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public bool Trashed
|
||||
{
|
||||
get => _trashed;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _trashed, Selectors.Trashed);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/Umbraco.Core/Models/Entities/TreeEntityPath.cs
Normal file
18
src/Umbraco.Core/Models/Entities/TreeEntityPath.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace Umbraco.Core.Models.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the path of a tree entity.
|
||||
/// </summary>
|
||||
public class TreeEntityPath
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier of the entity.
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the path of the entity.
|
||||
/// </summary>
|
||||
public string Path { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user