From 94febd5b876c0eeb8710e0672470188200017f26 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Fri, 5 Sep 2025 15:03:02 +0200 Subject: [PATCH 1/6] Move persistance of relations from repository into notification handlers. --- .../UmbracoBuilder.CoreServices.cs | 8 + .../Relations/ContentRelationsUpdate.cs | 159 ++++++++++++++++++ .../Implement/ContentRepositoryBase.cs | 82 +-------- .../Implement/DocumentRepository.cs | 4 - .../Repositories/Implement/MediaRepository.cs | 4 - .../Implement/MemberRepository.cs | 4 - 6 files changed, 168 insertions(+), 93 deletions(-) create mode 100644 src/Umbraco.Infrastructure/Persistence/Relations/ContentRelationsUpdate.cs diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index 70e5969d0483..39f0f132be41 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -52,6 +52,7 @@ using Umbraco.Cms.Infrastructure.Migrations.Install; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Mappers; +using Umbraco.Cms.Infrastructure.Persistence.Relations; using Umbraco.Cms.Infrastructure.PropertyEditors.NotificationHandlers; using Umbraco.Cms.Infrastructure.Routing; using Umbraco.Cms.Infrastructure.Runtime; @@ -435,6 +436,13 @@ public static IUmbracoBuilder AddCoreNotifications(this IUmbracoBuilder builder) .AddNotificationAsyncHandler() .AddNotificationAsyncHandler(); + // Handles for relation persistence on content save. + builder + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler(); + return builder; } diff --git a/src/Umbraco.Infrastructure/Persistence/Relations/ContentRelationsUpdate.cs b/src/Umbraco.Infrastructure/Persistence/Relations/ContentRelationsUpdate.cs new file mode 100644 index 000000000000..88bfaa655472 --- /dev/null +++ b/src/Umbraco.Infrastructure/Persistence/Relations/ContentRelationsUpdate.cs @@ -0,0 +1,159 @@ +using Microsoft.Extensions.Logging; +using NPoco; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Editors; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Persistence.Querying; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Cms.Infrastructure.Scoping; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Persistence.Relations; + +/// +/// Defines a notification handler for content saved operations that persists relations. +/// +internal class ContentRelationsUpdate : + IDistributedCacheNotificationHandler, + IDistributedCacheNotificationHandler, + IDistributedCacheNotificationHandler, + IDistributedCacheNotificationHandler +{ + private readonly IScopeProvider _scopeProvider; + private readonly DataValueReferenceFactoryCollection _dataValueReferenceFactories; + private readonly PropertyEditorCollection _propertyEditors; + private readonly IRelationRepository _relationRepository; + private readonly IRelationTypeRepository _relationTypeRepository; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + public ContentRelationsUpdate( + IScopeProvider scopeProvider, + DataValueReferenceFactoryCollection dataValueReferenceFactories, + PropertyEditorCollection propertyEditors, + IRelationRepository relationRepository, + IRelationTypeRepository relationTypeRepository, + ILogger logger) + { + _scopeProvider = scopeProvider; + _dataValueReferenceFactories = dataValueReferenceFactories; + _propertyEditors = propertyEditors; + _relationRepository = relationRepository; + _relationTypeRepository = relationTypeRepository; + _logger = logger; + } + + /// + public void Handle(ContentSavedNotification notification) => PersistRelations(notification.SavedEntities); + + /// + public void Handle(ContentPublishedNotification notification) => PersistRelations(notification.PublishedEntities); + + /// + public void Handle(MediaSavedNotification notification) => PersistRelations(notification.SavedEntities); + + /// + public void Handle(MemberSavedNotification notification) => PersistRelations(notification.SavedEntities); + + private void PersistRelations(IEnumerable entities) + { + using IScope scope = _scopeProvider.CreateScope(); + foreach (IContentBase entity in entities) + { + PersistRelations(scope, entity); + } + + scope.Complete(); + } + + private void PersistRelations(IScope scope, IContentBase entity) + { + // Get all references and automatic relation type aliases. + ISet references = _dataValueReferenceFactories.GetAllReferences(entity.Properties, _propertyEditors); + ISet automaticRelationTypeAliases = _dataValueReferenceFactories.GetAllAutomaticRelationTypesAliases(_propertyEditors); + + if (references.Count == 0) + { + // Delete all relations using the automatic relation type aliases. + _relationRepository.DeleteByParent(entity.Id, automaticRelationTypeAliases.ToArray()); + + // No need to add new references/relations + return; + } + + // Lookup all relation type IDs. + var relationTypeLookup = _relationTypeRepository.GetMany(Array.Empty()) + .Where(x => automaticRelationTypeAliases.Contains(x.Alias)) + .ToDictionary(x => x.Alias, x => x.Id); + + // Lookup node IDs for all GUID based UDIs. + IEnumerable keys = references.Select(x => x.Udi).OfType().Select(x => x.Guid); + var keysLookup = scope.Database.FetchByGroups(keys, Constants.Sql.MaxParameterCount, guids => + { + return scope.SqlContext.Sql() + .Select(x => x.NodeId, x => x.UniqueId) + .From() + .WhereIn(x => x.UniqueId, guids); + }).ToDictionary(x => x.UniqueId, x => x.NodeId); + + // Get all valid relations. + var relations = new List<(int ChildId, int RelationTypeId)>(references.Count); + foreach (UmbracoEntityReference reference in references) + { + if (string.IsNullOrEmpty(reference.RelationTypeAlias)) + { + // Reference does not specify a relation type alias, so skip adding a relation. + _logger.LogDebug("The reference to {Udi} does not specify a relation type alias, so it will not be saved as relation.", reference.Udi); + } + else if (!automaticRelationTypeAliases.Contains(reference.RelationTypeAlias)) + { + // Returning a reference that doesn't use an automatic relation type is an issue that should be fixed in code. + _logger.LogError("The reference to {Udi} uses a relation type {RelationTypeAlias} that is not an automatic relation type.", reference.Udi, reference.RelationTypeAlias); + } + else if (!relationTypeLookup.TryGetValue(reference.RelationTypeAlias, out int relationTypeId)) + { + // A non-existent relation type could be caused by an environment issue (e.g. it was manually removed). + _logger.LogWarning("The reference to {Udi} uses a relation type {RelationTypeAlias} that does not exist.", reference.Udi, reference.RelationTypeAlias); + } + else if (reference.Udi is not GuidUdi udi || !keysLookup.TryGetValue(udi.Guid, out var id)) + { + // Relations only support references to items that are stored in the NodeDto table (because of foreign key constraints). + _logger.LogInformation("The reference to {Udi} can not be saved as relation, because doesn't have a node ID.", reference.Udi); + } + else + { + relations.Add((id, relationTypeId)); + } + } + + // Get all existing relations (optimize for adding new and keeping existing relations). + IQuery query = scope.SqlContext.Query().Where(x => x.ParentId == entity.Id).WhereIn(x => x.RelationTypeId, relationTypeLookup.Values); + var existingRelations = _relationRepository.GetPagedRelationsByQuery(query, 0, int.MaxValue, out _, null) + .ToDictionary(x => (x.ChildId, x.RelationTypeId)); // Relations are unique by parent ID, child ID and relation type ID. + + // Add relations that don't exist yet. + IEnumerable relationsToAdd = relations.Except(existingRelations.Keys).Select(x => new ReadOnlyRelation(entity.Id, x.ChildId, x.RelationTypeId)); + _relationRepository.SaveBulk(relationsToAdd); + + // Delete relations that don't exist anymore. + foreach (IRelation relation in existingRelations.Where(x => !relations.Contains(x.Key)).Select(x => x.Value)) + { + _relationRepository.Delete(relation); + } + } + + private sealed class NodeIdKey + { + [Column("id")] + public int NodeId { get; set; } + + [Column("uniqueId")] + public Guid UniqueId { get; set; } + } +} diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs index bc5b2c79548a..fee28d5f8e96 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -1080,80 +1080,9 @@ protected virtual int GetReservedId(Guid uniqueId) #endregion + [Obsolete("This method is no longer used as the persistance of relations has been moved to a notification handler. Scheduled for removal in Umbraco 19.")] protected void PersistRelations(TEntity entity) { - // Get all references and automatic relation type aliases - ISet references = _dataValueReferenceFactories.GetAllReferences(entity.Properties, PropertyEditors); - ISet automaticRelationTypeAliases = _dataValueReferenceFactories.GetAllAutomaticRelationTypesAliases(PropertyEditors); - - if (references.Count == 0) - { - // Delete all relations using the automatic relation type aliases - RelationRepository.DeleteByParent(entity.Id, automaticRelationTypeAliases.ToArray()); - - // No need to add new references/relations - return; - } - - // Lookup all relation type IDs - var relationTypeLookup = RelationTypeRepository.GetMany(Array.Empty()) - .Where(x => automaticRelationTypeAliases.Contains(x.Alias)) - .ToDictionary(x => x.Alias, x => x.Id); - - // Lookup node IDs for all GUID based UDIs - IEnumerable keys = references.Select(x => x.Udi).OfType().Select(x => x.Guid); - var keysLookup = Database.FetchByGroups(keys, Constants.Sql.MaxParameterCount, guids => - { - return Sql() - .Select(x => x.NodeId, x => x.UniqueId) - .From() - .WhereIn(x => x.UniqueId, guids); - }).ToDictionary(x => x.UniqueId, x => x.NodeId); - - // Get all valid relations - var relations = new List<(int ChildId, int RelationTypeId)>(references.Count); - foreach (UmbracoEntityReference reference in references) - { - if (string.IsNullOrEmpty(reference.RelationTypeAlias)) - { - // Reference does not specify a relation type alias, so skip adding a relation - Logger.LogDebug("The reference to {Udi} does not specify a relation type alias, so it will not be saved as relation.", reference.Udi); - } - else if (!automaticRelationTypeAliases.Contains(reference.RelationTypeAlias)) - { - // Returning a reference that doesn't use an automatic relation type is an issue that should be fixed in code - Logger.LogError("The reference to {Udi} uses a relation type {RelationTypeAlias} that is not an automatic relation type.", reference.Udi, reference.RelationTypeAlias); - } - else if (!relationTypeLookup.TryGetValue(reference.RelationTypeAlias, out int relationTypeId)) - { - // A non-existent relation type could be caused by an environment issue (e.g. it was manually removed) - Logger.LogWarning("The reference to {Udi} uses a relation type {RelationTypeAlias} that does not exist.", reference.Udi, reference.RelationTypeAlias); - } - else if (reference.Udi is not GuidUdi udi || !keysLookup.TryGetValue(udi.Guid, out var id)) - { - // Relations only support references to items that are stored in the NodeDto table (because of foreign key constraints) - Logger.LogInformation("The reference to {Udi} can not be saved as relation, because doesn't have a node ID.", reference.Udi); - } - else - { - relations.Add((id, relationTypeId)); - } - } - - // Get all existing relations (optimize for adding new and keeping existing relations) - var query = Query().Where(x => x.ParentId == entity.Id).WhereIn(x => x.RelationTypeId, relationTypeLookup.Values); - var existingRelations = RelationRepository.GetPagedRelationsByQuery(query, 0, int.MaxValue, out _, null) - .ToDictionary(x => (x.ChildId, x.RelationTypeId)); // Relations are unique by parent ID, child ID and relation type ID - - // Add relations that don't exist yet - var relationsToAdd = relations.Except(existingRelations.Keys).Select(x => new ReadOnlyRelation(entity.Id, x.ChildId, x.RelationTypeId)); - RelationRepository.SaveBulk(relationsToAdd); - - // Delete relations that don't exist anymore - foreach (IRelation relation in existingRelations.Where(x => !relations.Contains(x.Key)).Select(x => x.Value)) - { - RelationRepository.Delete(relation); - } } /// @@ -1230,14 +1159,5 @@ protected void ReplacePropertyValues(TEntity entity, int versionId, int publishe Database.Execute(SqlContext.Sql().Delete().WhereIn(x => x.Id, existingPropDataIds)); } } - - private sealed class NodeIdKey - { - [Column("id")] - public int NodeId { get; set; } - - [Column("uniqueId")] - public Guid UniqueId { get; set; } - } } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs index 1279d621860b..428fd9bb007a 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs @@ -1074,8 +1074,6 @@ protected override void PersistNewItem(IContent entity) ClearEntityTags(entity, _tagRepository); } - PersistRelations(entity); - entity.ResetDirtyProperties(); // troubleshooting @@ -1325,8 +1323,6 @@ protected override void PersistUpdatedItem(IContent entity) ClearEntityTags(entity, _tagRepository); } - PersistRelations(entity); - // TODO: note re. tags: explicitly unpublished entities have cleared tags, but masked or trashed entities *still* have tags in the db - so what? } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs index 1b4dd2d8a9cc..d08607ec092d 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs @@ -416,8 +416,6 @@ protected override void PersistNewItem(IMedia entity) // set tags SetEntityTags(entity, _tagRepository, _serializer); - PersistRelations(entity); - OnUowRefreshedEntity(new MediaRefreshNotification(entity, new EventMessages())); entity.ResetDirtyProperties(); @@ -477,8 +475,6 @@ protected override void PersistUpdatedItem(IMedia entity) ReplacePropertyValues(entity, entity.VersionId, 0, out _, out _); SetEntityTags(entity, _tagRepository, _serializer); - - PersistRelations(entity); } OnUowRefreshedEntity(new MediaRefreshNotification(entity, new EventMessages())); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs index beff74a5e3d5..fc41bfdae91f 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs @@ -788,8 +788,6 @@ protected override void PersistNewItem(IMember entity) SetEntityTags(entity, _tagRepository, _jsonSerializer); - PersistRelations(entity); - OnUowRefreshedEntity(new MemberRefreshNotification(entity, new EventMessages())); entity.ResetDirtyProperties(); @@ -938,8 +936,6 @@ protected override void PersistUpdatedItem(IMember entity) SetEntityTags(entity, _tagRepository, _jsonSerializer); - PersistRelations(entity); - OnUowRefreshedEntity(new MemberRefreshNotification(entity, new EventMessages())); _memberByUsernameCachePolicy.DeleteByUserName(CacheKeys.MemberUserNameCachePrefix, entity.Username); From 700c969ef9f562fb1e96ca798f22d1cf341613aa Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Fri, 5 Sep 2025 16:30:51 +0200 Subject: [PATCH 2/6] Applied suggestions from code review. --- .../Persistence/Relations/ContentRelationsUpdate.cs | 12 ++++++++++++ .../Repositories/Implement/ContentRepositoryBase.cs | 5 ++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Infrastructure/Persistence/Relations/ContentRelationsUpdate.cs b/src/Umbraco.Infrastructure/Persistence/Relations/ContentRelationsUpdate.cs index 88bfaa655472..8547d49578c3 100644 --- a/src/Umbraco.Infrastructure/Persistence/Relations/ContentRelationsUpdate.cs +++ b/src/Umbraco.Infrastructure/Persistence/Relations/ContentRelationsUpdate.cs @@ -52,15 +52,27 @@ public ContentRelationsUpdate( /// public void Handle(ContentSavedNotification notification) => PersistRelations(notification.SavedEntities); + /// + public void Handle(IEnumerable notifications) => PersistRelations(notifications.SelectMany(x => x.SavedEntities)); + /// public void Handle(ContentPublishedNotification notification) => PersistRelations(notification.PublishedEntities); + /// + public void Handle(IEnumerable notifications) => PersistRelations(notifications.SelectMany(x => x.PublishedEntities)); + /// public void Handle(MediaSavedNotification notification) => PersistRelations(notification.SavedEntities); + /// + public void Handle(IEnumerable notifications) => PersistRelations(notifications.SelectMany(x => x.SavedEntities)); + /// public void Handle(MemberSavedNotification notification) => PersistRelations(notification.SavedEntities); + /// + public void Handle(IEnumerable notifications) => PersistRelations(notifications.SelectMany(x => x.SavedEntities)); + private void PersistRelations(IEnumerable entities) { using IScope scope = _scopeProvider.CreateScope(); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs index fee28d5f8e96..72983f973a66 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -1080,10 +1080,9 @@ protected virtual int GetReservedId(Guid uniqueId) #endregion - [Obsolete("This method is no longer used as the persistance of relations has been moved to a notification handler. Scheduled for removal in Umbraco 19.")] + [Obsolete("This method is no longer used as the persistance of relations has been moved to the ContentRelationsUpdate notification handler. Scheduled for removal in Umbraco 18.")] protected void PersistRelations(TEntity entity) - { - } + => Logger.LogWarning("ContentRepositoryBase.PersistRelations was called but this is now an obsolete, no-op method that is unused in Umbraco. No relations were peristed. Relations persistance has moved to the ContentRelationsUpdate notification handler.") /// /// Inserts property values for the content entity From 3ebbcc9c27b4677bd5d7fe9b425439dcd13ddd0d Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Fri, 5 Sep 2025 16:31:10 +0200 Subject: [PATCH 3/6] Apply suggestions from code review Co-authored-by: Ronald Barendse --- .../Persistence/Relations/ContentRelationsUpdate.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/Persistence/Relations/ContentRelationsUpdate.cs b/src/Umbraco.Infrastructure/Persistence/Relations/ContentRelationsUpdate.cs index 8547d49578c3..7d92863d7163 100644 --- a/src/Umbraco.Infrastructure/Persistence/Relations/ContentRelationsUpdate.cs +++ b/src/Umbraco.Infrastructure/Persistence/Relations/ContentRelationsUpdate.cs @@ -17,7 +17,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Relations; /// /// Defines a notification handler for content saved operations that persists relations. /// -internal class ContentRelationsUpdate : +internal sealed class ContentRelationsUpdate : IDistributedCacheNotificationHandler, IDistributedCacheNotificationHandler, IDistributedCacheNotificationHandler, From 4c2304ddf1c660f629f34ab9b6ed84327471e9a7 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Fri, 5 Sep 2025 16:34:07 +0200 Subject: [PATCH 4/6] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Persistence/Relations/ContentRelationsUpdate.cs | 2 +- .../Persistence/Repositories/Implement/ContentRepositoryBase.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Infrastructure/Persistence/Relations/ContentRelationsUpdate.cs b/src/Umbraco.Infrastructure/Persistence/Relations/ContentRelationsUpdate.cs index 7d92863d7163..871f530a1e4d 100644 --- a/src/Umbraco.Infrastructure/Persistence/Relations/ContentRelationsUpdate.cs +++ b/src/Umbraco.Infrastructure/Persistence/Relations/ContentRelationsUpdate.cs @@ -136,7 +136,7 @@ private void PersistRelations(IScope scope, IContentBase entity) else if (reference.Udi is not GuidUdi udi || !keysLookup.TryGetValue(udi.Guid, out var id)) { // Relations only support references to items that are stored in the NodeDto table (because of foreign key constraints). - _logger.LogInformation("The reference to {Udi} can not be saved as relation, because doesn't have a node ID.", reference.Udi); + _logger.LogInformation("The reference to {Udi} can not be saved as relation, because it doesn't have a node ID.", reference.Udi); } else { diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 72983f973a66..bf0476434355 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -1082,7 +1082,7 @@ protected virtual int GetReservedId(Guid uniqueId) [Obsolete("This method is no longer used as the persistance of relations has been moved to the ContentRelationsUpdate notification handler. Scheduled for removal in Umbraco 18.")] protected void PersistRelations(TEntity entity) - => Logger.LogWarning("ContentRepositoryBase.PersistRelations was called but this is now an obsolete, no-op method that is unused in Umbraco. No relations were peristed. Relations persistance has moved to the ContentRelationsUpdate notification handler.") + => Logger.LogWarning("ContentRepositoryBase.PersistRelations was called but this is now an obsolete, no-op method that is unused in Umbraco. No relations were persisted. Relations persistence has moved to the ContentRelationsUpdate notification handler.") /// /// Inserts property values for the content entity From 7badf02edd014e99d2fd02f5d5cb95c670cb0c38 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Fri, 5 Sep 2025 16:46:52 +0200 Subject: [PATCH 5/6] Fixed build. --- .../Repositories/Implement/ContentRepositoryBase.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs index bf0476434355..82fc8f911993 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -6,7 +6,6 @@ using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.Editors; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Core.Persistence.Querying; @@ -1082,7 +1081,7 @@ protected virtual int GetReservedId(Guid uniqueId) [Obsolete("This method is no longer used as the persistance of relations has been moved to the ContentRelationsUpdate notification handler. Scheduled for removal in Umbraco 18.")] protected void PersistRelations(TEntity entity) - => Logger.LogWarning("ContentRepositoryBase.PersistRelations was called but this is now an obsolete, no-op method that is unused in Umbraco. No relations were persisted. Relations persistence has moved to the ContentRelationsUpdate notification handler.") + => Logger.LogWarning("ContentRepositoryBase.PersistRelations was called but this is now an obsolete, no-op method that is unused in Umbraco. No relations were persisted. Relations persistence has moved to the ContentRelationsUpdate notification handler."); /// /// Inserts property values for the content entity From bb98ff29dc1abcb108d44cc38d99f7b68fb3fa5f Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sat, 6 Sep 2025 21:14:22 +0200 Subject: [PATCH 6/6] Fixed failing integration tests. --- .../Services/RelationServiceTests.cs | 12 +++++++++--- .../Services/TrackRelationsTests.cs | 14 ++++++++------ .../Services/TrackedReferencesServiceTests.cs | 9 +++++++++ 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/RelationServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/RelationServiceTests.cs index cd65b0d35622..f967c01039c4 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/RelationServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/RelationServiceTests.cs @@ -1,16 +1,15 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; -using System.Linq; using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Persistence.Relations; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; -using Umbraco.Extensions; namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; @@ -32,6 +31,13 @@ internal sealed class RelationServiceTests : UmbracoIntegrationTest private IRelationService RelationService => GetRequiredService(); + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + base.CustomTestSetup(builder); + builder + .AddNotificationHandler(); + } + [Test] public void Get_Paged_Relations_By_Relation_Type() { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/TrackRelationsTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/TrackRelationsTests.cs index ee05d4fef50b..c748b9781aad 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/TrackRelationsTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/TrackRelationsTests.cs @@ -3,7 +3,9 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Persistence.Relations; using Umbraco.Cms.Tests.Common.Attributes; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; @@ -29,11 +31,12 @@ internal sealed class TrackRelationsTests : UmbracoIntegrationTestWithContent private IRelationService RelationService => GetRequiredService(); - // protected override void CustomTestSetup(IUmbracoBuilder builder) - // { - // base.CustomTestSetup(builder); - // builder.AddNuCache(); - // } + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + base.CustomTestSetup(builder); + builder + .AddNotificationHandler(); + } [Test] [LongRunning] @@ -89,6 +92,5 @@ public void Automatically_Track_Relations() Assert.AreEqual(c1.Id, relations[2].ChildId); Assert.AreEqual(Constants.Conventions.RelationTypes.RelatedMemberAlias, relations[3].RelationType.Alias); Assert.AreEqual(member.Id, relations[3].ChildId); - } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/TrackedReferencesServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/TrackedReferencesServiceTests.cs index 3e74f5ccb59e..ae868d00fc14 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/TrackedReferencesServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/TrackedReferencesServiceTests.cs @@ -3,7 +3,9 @@ using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Persistence.Relations; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Builders.Extensions; using Umbraco.Cms.Tests.Common.Testing; @@ -25,6 +27,13 @@ internal class TrackedReferencesServiceTests : UmbracoIntegrationTest private IContentType ContentType { get; set; } + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + base.CustomTestSetup(builder); + builder + .AddNotificationHandler(); + } + [SetUp] public void Setup() => CreateTestData();