From 24ec0ee4cb310eff110258dacd32ac611d2cbe55 Mon Sep 17 00:00:00 2001 From: Sven Geusens Date: Wed, 6 Nov 2024 10:27:04 +0100 Subject: [PATCH] Optimize tracked references (#16996) * Optimed sql for checking if a list documents is tracked by a reference * Remove unoptimzed method and update Media controller too * Revert spacing, formatting and unneeded code change This partially reverts commit d32b6acf4fa2f167e40b789e0cd02ce355a3a8ed. * Cleanup temporary renaming * Add default implementations * Fix merge issue --------- Co-authored-by: Bjarke Berg --- .../AreReferencedDocumentController.cs | 7 +-- .../ReferencedByDocumentController.cs | 2 +- ...ReferencedDescendantsDocumentController.cs | 2 +- .../AreReferencedMediaController.cs | 5 ++- ...TrackedReferenceViewModelsMapDefinition.cs | 7 +++ .../ITrackedReferencesRepository.cs | 10 +++++ .../Services/ITrackedReferencesService.cs | 6 +++ .../Services/TrackedReferencesService.cs | 6 +++ .../Implement/TrackedReferencesRepository.cs | 45 +++++++++++++++++-- 9 files changed, 80 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/References/AreReferencedDocumentController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/References/AreReferencedDocumentController.cs index b73aa9ac94..ed593e9c9e 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/References/AreReferencedDocumentController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/References/AreReferencedDocumentController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.ViewModels; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; @@ -22,7 +23,7 @@ public class AreReferencedDocumentController : DocumentControllerBase } /// - /// Gets a page list of the items used in any kind of relation from selected keys. + /// Gets a paged list of the items used in any kind of relation from selected keys. /// /// /// Used when bulk deleting content/media and bulk unpublishing content (delete and unpublish on List view). @@ -37,11 +38,11 @@ public class AreReferencedDocumentController : DocumentControllerBase int skip = 0, int take = 20) { - PagedModel distinctByKeyItemsWithReferencedRelations = await _trackedReferencesSkipTakeService.GetPagedItemsWithRelationsAsync(ids, skip, take, true); + PagedModel distinctByKeyItemsWithReferencedRelations = await _trackedReferencesSkipTakeService.GetPagedKeysWithDependentReferencesAsync(ids, Constants.ObjectTypes.Document, skip, take); var pagedViewModel = new PagedViewModel { Total = distinctByKeyItemsWithReferencedRelations.Total, - Items = _umbracoMapper.MapEnumerable(distinctByKeyItemsWithReferencedRelations.Items), + Items = _umbracoMapper.MapEnumerable(distinctByKeyItemsWithReferencedRelations.Items), }; return await Task.FromResult(pagedViewModel); diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/References/ReferencedByDocumentController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/References/ReferencedByDocumentController.cs index 0e7e05a29f..a818e70af8 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/References/ReferencedByDocumentController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/References/ReferencedByDocumentController.cs @@ -22,7 +22,7 @@ public class ReferencedByDocumentController : DocumentControllerBase } /// - /// Gets a page list of tracked references for the current item, so you can see where an item is being used. + /// Gets a paged list of tracked references for the current item, so you can see where an item is being used. /// /// /// Used by info tabs on content, media etc. and for the delete and unpublish of single items. diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/References/ReferencedDescendantsDocumentController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/References/ReferencedDescendantsDocumentController.cs index 7ee7bae77f..4b931632e1 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/References/ReferencedDescendantsDocumentController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/References/ReferencedDescendantsDocumentController.cs @@ -22,7 +22,7 @@ public class ReferencedDescendantsDocumentController : DocumentControllerBase } /// - /// Gets a page list of the child nodes of the current item used in any kind of relation. + /// Gets a paged list of the descendant nodes of the current item used in any kind of relation. /// /// /// Used when deleting and unpublishing a single item to check if this item has any descending items that are in any diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/References/AreReferencedMediaController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/References/AreReferencedMediaController.cs index 4ec013a0e7..89e0c2d1ba 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Media/References/AreReferencedMediaController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/References/AreReferencedMediaController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.ViewModels; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; @@ -38,11 +39,11 @@ public class AreReferencedMediaController : MediaControllerBase int skip = 0, int take = 20) { - PagedModel distinctByKeyItemsWithReferencedRelations = await _trackedReferencesSkipTakeService.GetPagedItemsWithRelationsAsync(ids, skip, take, true); + PagedModel distinctByKeyItemsWithReferencedRelations = await _trackedReferencesSkipTakeService.GetPagedKeysWithDependentReferencesAsync(ids, Constants.ObjectTypes.Media, skip, take); var pagedViewModel = new PagedViewModel { Total = distinctByKeyItemsWithReferencedRelations.Total, - Items = _umbracoMapper.MapEnumerable(distinctByKeyItemsWithReferencedRelations.Items), + Items = _umbracoMapper.MapEnumerable(distinctByKeyItemsWithReferencedRelations.Items), }; return await Task.FromResult(pagedViewModel); diff --git a/src/Umbraco.Cms.Api.Management/Mapping/TrackedReferences/TrackedReferenceViewModelsMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/TrackedReferences/TrackedReferenceViewModelsMapDefinition.cs index 5d53d120a2..6a4fe40c62 100644 --- a/src/Umbraco.Cms.Api.Management/Mapping/TrackedReferences/TrackedReferenceViewModelsMapDefinition.cs +++ b/src/Umbraco.Cms.Api.Management/Mapping/TrackedReferences/TrackedReferenceViewModelsMapDefinition.cs @@ -13,6 +13,7 @@ public class TrackedReferenceViewModelsMapDefinition : IMapDefinition mapper.Define((source, context) => new MediaReferenceResponseModel(), Map); mapper.Define((source, context) => new DefaultReferenceResponseModel(), Map); mapper.Define((source, context) => new ReferenceByIdModel(), Map); + mapper.Define((source, context) => new ReferenceByIdModel(), Map); } // Umbraco.Code.MapAll @@ -56,4 +57,10 @@ public class TrackedReferenceViewModelsMapDefinition : IMapDefinition { target.Id = source.NodeKey; } + + // Umbraco.Code.MapAll + private void Map(Guid source, ReferenceByIdModel target, MapperContext context) + { + target.Id = source; + } } diff --git a/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs index 4442e5ecf2..01bff9f356 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs @@ -131,4 +131,14 @@ public interface ITrackedReferencesRepository long take, bool filterMustBeIsDependency, out long totalRecords); + + Task> GetPagedNodeKeysWithDependantReferencesAsync( + ISet keys, + Guid nodeObjectTypeId, + long skip, + long take) + { + IEnumerable pagedItems = GetPagedItemsWithRelations(keys, skip, take, true, out var total); + return Task.FromResult(new PagedModel(total, pagedItems.Select(i => i.NodeKey))); + } } diff --git a/src/Umbraco.Core/Services/ITrackedReferencesService.cs b/src/Umbraco.Core/Services/ITrackedReferencesService.cs index 7cfcd1c117..c60156d31b 100644 --- a/src/Umbraco.Core/Services/ITrackedReferencesService.cs +++ b/src/Umbraco.Core/Services/ITrackedReferencesService.cs @@ -97,4 +97,10 @@ public interface ITrackedReferencesService /// A paged result of objects. Task> GetPagedItemsWithRelationsAsync(ISet keys, long skip, long take, bool filterMustBeIsDependency); + + Task> GetPagedKeysWithDependentReferencesAsync(ISet keys, Guid nodeObjectTypeId, long skip, long take) + { + PagedModel pagedItems = GetPagedItemsWithRelationsAsync(keys, skip, take, true).GetAwaiter().GetResult(); + return Task.FromResult(new PagedModel(pagedItems.Total, pagedItems.Items.Select(i => i.NodeKey))); + } } diff --git a/src/Umbraco.Core/Services/TrackedReferencesService.cs b/src/Umbraco.Core/Services/TrackedReferencesService.cs index 06b8fc5510..5d5d76f7f0 100644 --- a/src/Umbraco.Core/Services/TrackedReferencesService.cs +++ b/src/Umbraco.Core/Services/TrackedReferencesService.cs @@ -141,4 +141,10 @@ public class TrackedReferencesService : ITrackedReferencesService return await Task.FromResult(pagedModel); } + + public async Task> GetPagedKeysWithDependentReferencesAsync(ISet keys, Guid objectTypeId, long skip, long take) + { + using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); + return await _trackedReferencesRepository.GetPagedNodeKeysWithDependantReferencesAsync(keys, objectTypeId, skip, take); + } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs index 90e3fde207..2b7a43589c 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs @@ -477,10 +477,49 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement return _umbracoMapper.MapEnumerable(pagedResult); } - public IEnumerable GetPagedDescendantsInReferences( - Guid parentKey, + public async Task> GetPagedNodeKeysWithDependantReferencesAsync( + ISet keys, + Guid nodeObjectTypeId, long skip, - long take, + long take) + { + if (_scopeAccessor.AmbientScope is null) + { + throw new InvalidOperationException("Can not execute without a valid AmbientScope"); + } + + Sql? sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql() + .SelectDistinct(node => node.UniqueId) + .From() + .InnerJoin() + .On((node, relation) => + node.NodeId == relation.ParentId || node.NodeId == relation.ParentId || node.NodeId == relation.ChildId) + .InnerJoin() + .On((relation, relationType) => relation.RelationType == relationType.Id && relationType.IsDependency) + .Where( + (node, relation, relationType) + => node.NodeObjectType == nodeObjectTypeId + && keys.Contains(node.UniqueId) + && (node.NodeId == relation.ChildId + || (relationType.Dual && relation.ParentId == node.NodeId))); + + var totalRecords = _scopeAccessor.AmbientScope.Database.Count(sql); + + // no need to process further if no records are found + if (totalRecords < 1) + { + return new PagedModel(totalRecords, Enumerable.Empty()); + } + + // Ordering is required for paging + sql = sql.OrderBy(node => node.UniqueId); + + IEnumerable result = await _scopeAccessor.AmbientScope.Database.SkipTakeAsync(skip, take, sql); + + return new PagedModel(totalRecords, result); + } + + public IEnumerable GetPagedDescendantsInReferences(Guid parentKey, long skip, long take, bool filterMustBeIsDependency, out long totalRecords) {