There was a memory leak with PublicAccessEntry during even unassignment which was not clearing the correct handler. This goes a step further and adds a new ClearCollectionChangedEvents method for all observable collections used in umbraco which allows fully clearing ALL event handlers instead of having to track specific ones. This will ensure there are no unintended memory leaks in case end-users have assigned event handlers to the collection changed event which would not be unassigned during deep cloning.
113 lines
3.7 KiB
C#
113 lines
3.7 KiB
C#
using System;
|
|
using System.Collections.Specialized;
|
|
using System.Diagnostics;
|
|
using System.Runtime.Serialization;
|
|
using Umbraco.Core.Models.Entities;
|
|
|
|
namespace Umbraco.Core.Models
|
|
{
|
|
/// <summary>
|
|
/// A group of property types, which corresponds to the properties grouped under a Tab.
|
|
/// </summary>
|
|
[Serializable]
|
|
[DataContract(IsReference = true)]
|
|
[DebuggerDisplay("Id: {Id}, Name: {Name}")]
|
|
public class PropertyGroup : EntityBase, IEquatable<PropertyGroup>
|
|
{
|
|
private string _name;
|
|
private int _sortOrder;
|
|
private PropertyTypeCollection _propertyTypes;
|
|
|
|
public PropertyGroup(bool isPublishing)
|
|
: this(new PropertyTypeCollection(isPublishing))
|
|
{ }
|
|
|
|
public PropertyGroup(PropertyTypeCollection propertyTypeCollection)
|
|
{
|
|
PropertyTypes = propertyTypeCollection;
|
|
}
|
|
|
|
private void PropertyTypesChanged(object sender, NotifyCollectionChangedEventArgs e)
|
|
{
|
|
OnPropertyChanged(nameof(PropertyTypes));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the Name of the Group, which corresponds to the Tab-name in the UI
|
|
/// </summary>
|
|
[DataMember]
|
|
public string Name
|
|
{
|
|
get => _name;
|
|
set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the Sort Order of the Group
|
|
/// </summary>
|
|
[DataMember]
|
|
public int SortOrder
|
|
{
|
|
get => _sortOrder;
|
|
set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, nameof(SortOrder));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a collection of PropertyTypes for this PropertyGroup
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Marked DoNotClone because we will manually deal with cloning and the event handlers
|
|
/// </remarks>
|
|
[DataMember]
|
|
[DoNotClone]
|
|
public PropertyTypeCollection PropertyTypes
|
|
{
|
|
get => _propertyTypes;
|
|
set
|
|
{
|
|
if (_propertyTypes != null)
|
|
{
|
|
_propertyTypes.ClearCollectionChangedEvents();
|
|
}
|
|
|
|
_propertyTypes = value;
|
|
|
|
// since we're adding this collection to this group,
|
|
// we need to ensure that all the lazy values are set.
|
|
foreach (var propertyType in _propertyTypes)
|
|
propertyType.PropertyGroupId = new Lazy<int>(() => Id);
|
|
|
|
OnPropertyChanged(nameof(PropertyTypes));
|
|
_propertyTypes.CollectionChanged += PropertyTypesChanged;
|
|
}
|
|
}
|
|
|
|
public bool Equals(PropertyGroup other)
|
|
{
|
|
if (base.Equals(other)) return true;
|
|
return other != null && Name.InvariantEquals(other.Name);
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
var baseHash = base.GetHashCode();
|
|
var nameHash = Name.ToLowerInvariant().GetHashCode();
|
|
return baseHash ^ nameHash;
|
|
}
|
|
|
|
protected override void PerformDeepClone(object clone)
|
|
{
|
|
base.PerformDeepClone(clone);
|
|
|
|
var clonedEntity = (PropertyGroup)clone;
|
|
|
|
if (clonedEntity._propertyTypes != null)
|
|
{
|
|
clonedEntity._propertyTypes.ClearCollectionChangedEvents(); //clear this event handler if any
|
|
clonedEntity._propertyTypes = (PropertyTypeCollection) _propertyTypes.DeepClone(); //manually deep clone
|
|
clonedEntity._propertyTypes.CollectionChanged += clonedEntity.PropertyTypesChanged; //re-assign correct event handler
|
|
}
|
|
}
|
|
}
|
|
}
|