Remove "additional data" from entities (#16024)

* Remove "additional data" from entities

* Fix merge issue

---------

Co-authored-by: Bjarke Berg <mail@bergmania.dk>
This commit is contained in:
Kenn Jacobsen
2024-04-11 10:55:21 +02:00
committed by GitHub
parent faee63627f
commit acae5f2d57
20 changed files with 48 additions and 243 deletions

View File

@@ -254,7 +254,9 @@ namespace Umbraco.Cms.Core.DependencyInjection
Services.AddSingleton<IPublishedModelFactory>(factory => factory.CreateDefaultPublishedModelFactory());
Services
.AddNotificationAsyncHandler<MemberGroupSavingNotification, PublicAccessHandler>()
.AddNotificationHandler<MemberGroupSavedNotification, PublicAccessHandler>()
.AddNotificationAsyncHandler<MemberGroupDeletingNotification, PublicAccessHandler>()
.AddNotificationHandler<MemberGroupDeletedNotification, PublicAccessHandler>();
Services.AddSingleton<ISyncBootStateAccessor, NonRuntimeLevelBootStateAccessor>();

View File

@@ -7,32 +7,61 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Handlers;
public sealed class PublicAccessHandler :
INotificationAsyncHandler<MemberGroupSavingNotification>,
INotificationHandler<MemberGroupSavedNotification>,
INotificationAsyncHandler<MemberGroupDeletingNotification>,
INotificationHandler<MemberGroupDeletedNotification>
{
private readonly IPublicAccessService _publicAccessService;
private readonly IMemberGroupService _memberGroupService;
public PublicAccessHandler(IPublicAccessService publicAccessService) =>
_publicAccessService = publicAccessService ?? throw new ArgumentNullException(nameof(publicAccessService));
public PublicAccessHandler(IPublicAccessService publicAccessService, IMemberGroupService memberGroupService)
{
_publicAccessService = publicAccessService;
_memberGroupService = memberGroupService;
}
public void Handle(MemberGroupDeletedNotification notification) => Handle(notification.DeletedEntities);
public async Task HandleAsync(MemberGroupSavingNotification notification, CancellationToken cancellationToken)
=> await SaveStateAsync(notification.SavedEntities, notification);
public void Handle(MemberGroupSavedNotification notification) => Handle(notification.SavedEntities);
public async Task HandleAsync(MemberGroupDeletingNotification notification, CancellationToken cancellationToken)
=> await SaveStateAsync(notification.DeletedEntities, notification);
private void Handle(IEnumerable<IMemberGroup> affectedEntities)
public void Handle(MemberGroupDeletedNotification notification) => Handle(notification.DeletedEntities, notification);
public void Handle(MemberGroupSavedNotification notification) => Handle(notification.SavedEntities, notification);
private async Task SaveStateAsync(IEnumerable<IMemberGroup> affectedEntities, IStatefulNotification notification)
{
foreach (IMemberGroup memberGroup in affectedEntities)
{
// store the current group name in the notification state
var currentMemberGroupName = (await _memberGroupService.GetAsync(memberGroup.Key))?.Name;
if (currentMemberGroupName.IsNullOrWhiteSpace())
{
continue;
}
notification.State[StateKey(memberGroup)] = currentMemberGroupName;
}
}
private void Handle(IEnumerable<IMemberGroup> affectedEntities, IStatefulNotification notification)
{
foreach (IMemberGroup grp in affectedEntities)
{
// check if the name has changed
if ((grp.AdditionalData?.ContainsKey("previousName") ?? false)
&& grp.AdditionalData["previousName"] != null
&& grp.AdditionalData["previousName"]?.ToString().IsNullOrWhiteSpace() == false
&& grp.AdditionalData["previousName"]?.ToString() != grp.Name)
// check if the group name has changed and update group based rules accordingly
if (notification.State.TryGetValue(StateKey(grp), out var stateValue) is false
|| stateValue is not string previousName
|| previousName.IsNullOrWhiteSpace()
|| previousName == grp.Name)
{
_publicAccessService.RenameMemberGroupRoleRules(
grp.AdditionalData["previousName"]?.ToString(),
grp.Name);
continue;
}
_publicAccessService.RenameMemberGroupRoleRules(previousName, grp.Name);
}
}
private static string StateKey(IMemberGroup memberGroup) => $"{nameof(PublicAccessHandler)}:{memberGroup.Key}";
}

View File

@@ -22,8 +22,6 @@ public class EntitySlim : IEntitySlim
/// </summary>
public static readonly IEntitySlim Root = new EntitySlim { Path = "-1", Name = "root", HasChildren = true };
private IDictionary<string, object?>? _additionalData;
// implement IEntity
/// <inheritdoc />
@@ -84,17 +82,6 @@ public class EntitySlim : IEntitySlim
[DataMember]
public bool Trashed { get; set; }
// implement IUmbracoEntity
/// <inheritdoc />
[DataMember]
public IDictionary<string, object?>? AdditionalData =>
_additionalData ??= new Dictionary<string, object?>();
/// <inheritdoc />
[IgnoreDataMember]
public bool HasAdditionalData => _additionalData != null;
// implement IEntitySlim
/// <inheritdoc />

View File

@@ -3,7 +3,7 @@ namespace Umbraco.Cms.Core.Models.Entities;
/// <summary>
/// Represents a lightweight entity, managed by the entity service.
/// </summary>
public interface IEntitySlim : IUmbracoEntity, IHaveAdditionalData
public interface IEntitySlim : IUmbracoEntity
{
/// <summary>
/// Gets or sets the entity object type.

View File

@@ -1,43 +0,0 @@
namespace Umbraco.Cms.Core.Models.Entities;
/// <summary>
/// Provides support for additional data.
/// </summary>
/// <remarks>
/// <para>Additional data are transient, not deep-cloned.</para>
/// </remarks>
public interface IHaveAdditionalData
{
/// <summary>
/// Gets additional data for this entity.
/// </summary>
/// <remarks>
/// Can be empty, but never null. To avoid allocating, do not
/// test for emptiness, 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; }
// how to implement:
/*
private IDictionary<string, object> _additionalData;
/// <inheritdoc />
[DataMember]
[DoNotClone]
PublicAccessEntry IDictionary<string, object> AdditionalData => _additionalData ?? (_additionalData = new Dictionary<string, object>());
/// <inheritdoc />
[IgnoreDataMember]
PublicAccessEntry bool HasAdditionalData => _additionalData != null;
*/
}

View File

@@ -1,27 +0,0 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Umbraco.Cms.Core.Models.Entities;
namespace Umbraco.Extensions;
public static class HaveAdditionalDataExtensions
{
/// <summary>
/// Gets additional data.
/// </summary>
public static object? GetAdditionalDataValueIgnoreCase(this IHaveAdditionalData entity, string key, object? defaultValue)
{
if (!entity.HasAdditionalData)
{
return defaultValue;
}
if (entity.AdditionalData?.ContainsKeyIgnoreCase(key) == false)
{
return defaultValue;
}
return entity.AdditionalData?.GetValueIgnoreCase(key, defaultValue);
}
}

View File

@@ -1,10 +1,9 @@
using System.ComponentModel;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Models.Membership;
namespace Umbraco.Cms.Core.Models;
public interface IMember : IContentBase, IMembershipUser, IHaveAdditionalData
public interface IMember : IContentBase, IMembershipUser
{
/// <summary>
/// String alias of the default ContentType

View File

@@ -5,7 +5,7 @@ namespace Umbraco.Cms.Core.Models;
/// <summary>
/// Represents a member type
/// </summary>
public interface IMemberGroup : IEntity, IRememberBeingDirty, IHaveAdditionalData
public interface IMemberGroup : IEntity, IRememberBeingDirty
{
/// <summary>
/// The name of the member group

View File

@@ -11,7 +11,6 @@ namespace Umbraco.Cms.Core.Models;
[DataContract(IsReference = true)]
public class Member : ContentBase, IMember
{
private IDictionary<string, object?>? _additionalData;
private string _email;
private DateTime? _emailConfirmedDate;
private int _failedPasswordAttempts;
@@ -508,15 +507,6 @@ public class Member : ContentBase, IMember
[EditorBrowsable(EditorBrowsableState.Never)]
public string? PropertyTypeAlias { get; set; }
/// <inheritdoc />
[DataMember]
[DoNotClone]
public IDictionary<string, object?> AdditionalData => _additionalData ??= new Dictionary<string, object?>();
/// <inheritdoc />
[IgnoreDataMember]
public bool HasAdditionalData => _additionalData != null;
private Attempt<T> WarnIfPropertyTypeNotFoundOnGet<T>(string propertyAlias, string propertyName, T defaultVal)
{
static void DoLog(string logPropertyAlias, string logPropertyName)

View File

@@ -10,36 +10,14 @@ namespace Umbraco.Cms.Core.Models;
[DataContract(IsReference = true)]
public class MemberGroup : EntityBase, IMemberGroup
{
private IDictionary<string, object?>? _additionalData;
private int _creatorId;
private string? _name;
/// <inheritdoc />
[DataMember]
[DoNotClone]
public IDictionary<string, object?> AdditionalData =>
_additionalData ??= new Dictionary<string, object?>();
/// <inheritdoc />
[IgnoreDataMember]
public bool HasAdditionalData => _additionalData != null;
[DataMember]
public string? Name
{
get => _name;
set
{
if (_name != value)
{
// if the name has changed, add the value to the additional data,
// this is required purely for event handlers to know the previous name of the group
// so we can keep the public access up to date.
AdditionalData["previousName"] = _name;
}
SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name));
}
set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name));
}
[DataMember]

View File

@@ -1,7 +1,6 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Tests.Common.Builders.Interfaces;
@@ -20,7 +19,6 @@ public class DocumentEntitySlimBuilder
IWithSortOrderBuilder,
IWithParentIdBuilder
{
private GenericDictionaryBuilder<DocumentEntitySlimBuilder, string, object> _additionalDataBuilder;
private string _contentTypeAlias;
private string _contentTypeIcon;
private string _contentTypeThumbnail;
@@ -128,13 +126,6 @@ public class DocumentEntitySlimBuilder
return this;
}
public GenericDictionaryBuilder<DocumentEntitySlimBuilder, string, object> AddAdditionalData()
{
var builder = new GenericDictionaryBuilder<DocumentEntitySlimBuilder, string, object>(this);
_additionalDataBuilder = builder;
return builder;
}
public override DocumentEntitySlim Build()
{
var id = _id ?? 1;
@@ -172,15 +163,6 @@ public class DocumentEntitySlimBuilder
Published = published
};
if (_additionalDataBuilder != null)
{
var additionalData = _additionalDataBuilder.Build();
foreach (var kvp in additionalData)
{
documentEntitySlim.AdditionalData.Add(kvp.Key, kvp.Value);
}
}
return documentEntitySlim;
}
}

View File

@@ -1,8 +1,6 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System;
using System.Collections.Generic;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Tests.Common.Builders.Extensions;
using Umbraco.Cms.Tests.Common.Builders.Interfaces;
@@ -24,7 +22,6 @@ public class MemberBuilder
IWithSortOrderBuilder,
IAccountBuilder
{
private GenericDictionaryBuilder<MemberBuilder, string, object> _additionalDataBuilder;
private DateTime? _createDate;
private int? _creatorId;
private string _email;
@@ -201,13 +198,6 @@ public class MemberBuilder
return builder;
}
public GenericDictionaryBuilder<MemberBuilder, string, object> AddAdditionalData()
{
var builder = new GenericDictionaryBuilder<MemberBuilder, string, object>(this);
_additionalDataBuilder = builder;
return builder;
}
public GenericDictionaryBuilder<MemberBuilder, string, object> AddPropertyData()
{
var builder = new GenericDictionaryBuilder<MemberBuilder, string, object>(this);
@@ -281,15 +271,6 @@ public class MemberBuilder
member.Groups = _memberGroupsBuilder.Build();
}
if (_additionalDataBuilder != null)
{
var additionalData = _additionalDataBuilder.Build();
foreach (var kvp in additionalData)
{
member.AdditionalData.Add(kvp.Key, kvp.Value);
}
}
if (_propertyDataBuilder != null)
{
var propertyData = _propertyDataBuilder.Build();

View File

@@ -1,7 +1,6 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Tests.Common.Builders.Interfaces;
@@ -16,7 +15,6 @@ public class MemberGroupBuilder
IWithUpdateDateBuilder,
IWithNameBuilder
{
private GenericDictionaryBuilder<MemberGroupBuilder, string, object> _additionalDataBuilder;
private DateTime? _createDate;
private int? _creatorId;
@@ -61,13 +59,6 @@ public class MemberGroupBuilder
set => _updateDate = value;
}
public GenericDictionaryBuilder<MemberGroupBuilder, string, object> AddAdditionalData()
{
var builder = new GenericDictionaryBuilder<MemberGroupBuilder, string, object>(this);
_additionalDataBuilder = builder;
return builder;
}
public override MemberGroup Build()
{
var id = _id ?? 1;
@@ -87,15 +78,6 @@ public class MemberGroupBuilder
CreatorId = creatorId
};
if (_additionalDataBuilder != null)
{
var additionalData = _additionalDataBuilder.Build();
foreach (var kvp in additionalData)
{
memberGroup.AdditionalData.Add(kvp.Key, kvp.Value);
}
}
return memberGroup;
}
}

View File

@@ -690,8 +690,6 @@ public class EntityServiceTests : UmbracoIntegrationTest
for (var i = 0; i < entities.Length; i++)
{
Assert.AreEqual(0, entities[i].AdditionalData.Count);
if (i % 2 == 0)
{
var doc = (IDocumentEntitySlim)entities[i];
@@ -702,10 +700,6 @@ public class EntityServiceTests : UmbracoIntegrationTest
Assert.AreEqual(_langEs.IsoCode.ToLowerInvariant(), keys[1].ToLowerInvariant());
Assert.AreEqual("Test " + i + " - ES", vals[1]);
}
else
{
Assert.AreEqual(0, entities[i].AdditionalData.Count);
}
}
}

View File

@@ -32,10 +32,6 @@ public class DocumentEntityTests
.WithContentTypeThumbnail("thumb")
.WithHasChildren(true)
.WithPublished(true)
.AddAdditionalData()
.WithKeyValue("test1", 3)
.WithKeyValue("test2", "value")
.Done()
.Build();
var json = JsonSerializer.Serialize(item);

View File

@@ -31,8 +31,6 @@ public class MemberGroupTests
Assert.AreNotSame(clone, group);
Assert.AreEqual(clone, group);
Assert.AreEqual(clone.Id, group.Id);
Assert.AreEqual(clone.AdditionalData, group.AdditionalData);
Assert.AreEqual(clone.AdditionalData.Count, group.AdditionalData.Count);
Assert.AreEqual(clone.CreateDate, group.CreateDate);
Assert.AreEqual(clone.CreatorId, group.CreatorId);
Assert.AreEqual(clone.Key, group.Key);
@@ -64,9 +62,5 @@ public class MemberGroupTests
.WithCreatorId(4)
.WithCreateDate(DateTime.Now)
.WithUpdateDate(DateTime.Now)
.AddAdditionalData()
.WithKeyValue("test1", 123)
.WithKeyValue("test2", "hello")
.Done()
.Build();
}

View File

@@ -32,7 +32,6 @@ public class MemberTests
Assert.AreEqual(clone, member);
Assert.AreEqual(clone.Id, member.Id);
Assert.AreEqual(clone.VersionId, member.VersionId);
Assert.AreEqual(clone.AdditionalData, member.AdditionalData);
Assert.AreEqual(clone.ContentType, member.ContentType);
Assert.AreEqual(clone.ContentTypeId, member.ContentTypeId);
Assert.AreEqual(clone.CreateDate, member.CreateDate);
@@ -123,10 +122,6 @@ public class MemberTests
.WithValue("Group 1")
.WithValue("Group 2")
.Done()
.AddAdditionalData()
.WithKeyValue("test1", 123)
.WithKeyValue("test2", "hello")
.Done()
.WithPropertyIdsIncrementingFrom(200)
.AddPropertyData()
.WithKeyValue("title", "Name member")

View File

@@ -1,7 +1,6 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System.Collections.Generic;
using NUnit.Framework;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Builders.Extensions;
@@ -30,8 +29,6 @@ public class DocumentEntitySlimBuilderTests
var testKey = Guid.NewGuid();
var testCreateDate = DateTime.Now.AddHours(-1);
var testUpdateDate = DateTime.Now;
var testAdditionalData1 = new KeyValuePair<string, object>("test1", 123);
var testAdditionalData2 = new KeyValuePair<string, object>("test2", "hello");
var builder = new DocumentEntitySlimBuilder();
@@ -52,10 +49,6 @@ public class DocumentEntitySlimBuilderTests
.WithContentTypeThumbnail(testContentTypeThumbnail)
.WithHasChildren(testHasChildren)
.WithPublished(testPublished)
.AddAdditionalData()
.WithKeyValue(testAdditionalData1.Key, testAdditionalData1.Value)
.WithKeyValue(testAdditionalData2.Key, testAdditionalData2.Value)
.Done()
.Build();
// Assert
@@ -74,8 +67,5 @@ public class DocumentEntitySlimBuilderTests
Assert.AreEqual(testContentTypeThumbnail, item.ContentTypeThumbnail);
Assert.AreEqual(testHasChildren, item.HasChildren);
Assert.AreEqual(testPublished, item.Published);
Assert.AreEqual(2, item.AdditionalData.Count);
Assert.AreEqual(testAdditionalData1.Value, item.AdditionalData[testAdditionalData1.Key]);
Assert.AreEqual(testAdditionalData2.Value, item.AdditionalData[testAdditionalData2.Key]);
}
}

View File

@@ -1,8 +1,6 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Tests.Common.Builders;
@@ -56,8 +54,6 @@ public class MemberBuilderTests
var testPropertyData1 = new KeyValuePair<string, object>("title", "Name member");
var testPropertyData2 = new KeyValuePair<string, object>("bodyText", "This is a subpage");
var testPropertyData3 = new KeyValuePair<string, object>("author", "John Doe");
var testAdditionalData1 = new KeyValuePair<string, object>("test1", 123);
var testAdditionalData2 = new KeyValuePair<string, object>("test2", "hello");
const int testPropertyIdsIncrementingFrom = 200;
var builder = new MemberBuilder();
@@ -116,10 +112,6 @@ public class MemberBuilderTests
.WithValue(testGroups[0])
.WithValue(testGroups[1])
.Done()
.AddAdditionalData()
.WithKeyValue(testAdditionalData1.Key, testAdditionalData1.Value)
.WithKeyValue(testAdditionalData2.Key, testAdditionalData2.Value)
.Done()
.WithPropertyIdsIncrementingFrom(200)
.AddPropertyData()
.WithKeyValue(testPropertyData1.Key, testPropertyData1.Value)
@@ -153,9 +145,5 @@ public class MemberBuilderTests
var propertyIds = member.Properties.Select(x => x.Id).OrderBy(x => x).ToArray();
Assert.AreEqual(testPropertyIdsIncrementingFrom + 1, propertyIds.Min());
Assert.AreEqual(testPropertyIdsIncrementingFrom + 4, propertyIds.Max());
Assert.AreEqual(2, member.AdditionalData.Count);
Assert.AreEqual(testAdditionalData1.Value, member.AdditionalData[testAdditionalData1.Key]);
Assert.AreEqual(testAdditionalData2.Value, member.AdditionalData[testAdditionalData2.Key]);
}
}

View File

@@ -1,7 +1,6 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System.Collections.Generic;
using NUnit.Framework;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Builders.Extensions;
@@ -21,8 +20,6 @@ public class MemberGroupBuilderTests
var testKey = Guid.NewGuid();
var testCreateDate = DateTime.Now.AddHours(-1);
var testUpdateDate = DateTime.Now;
var testAdditionalData1 = new KeyValuePair<string, object>("test1", 123);
var testAdditionalData2 = new KeyValuePair<string, object>("test2", "hello");
var builder = new MemberGroupBuilder();
@@ -34,10 +31,6 @@ public class MemberGroupBuilderTests
.WithCreatorId(testCreatorId)
.WithCreateDate(testCreateDate)
.WithUpdateDate(testUpdateDate)
.AddAdditionalData()
.WithKeyValue(testAdditionalData1.Key, testAdditionalData1.Value)
.WithKeyValue(testAdditionalData2.Key, testAdditionalData2.Value)
.Done()
.Build();
// Assert
@@ -47,10 +40,5 @@ public class MemberGroupBuilderTests
Assert.AreEqual(testCreateDate, group.CreateDate);
Assert.AreEqual(testUpdateDate, group.UpdateDate);
Assert.AreEqual(testCreatorId, group.CreatorId);
// previousName is added as part of the MemberGroup construction, plus the 2 we've added.
Assert.AreEqual(3, group.AdditionalData.Count);
Assert.AreEqual(testAdditionalData1.Value, group.AdditionalData[testAdditionalData1.Key]);
Assert.AreEqual(testAdditionalData2.Value, group.AdditionalData[testAdditionalData2.Key]);
}
}