Elements: Invalidate the id/key map when an element container is deleted (closes #23072)#23074
Conversation
…p is invalidated.
|
Claude finished @AndyButland's task in 3m 33s —— View job PR ReviewTarget: Fixes a stale
Suggestions
Approved with Suggestions for improvementGood to go, but please carefully consider the importance of the suggestions. The implementation is clean and follows established patterns exactly ( |
There was a problem hiding this comment.
Pull request overview
This PR fixes a stale IIdKeyMap entry scenario for element containers (folders) by ensuring container deletions trigger distributed cache invalidation, so a container recreated under the same GUID resolves to the new integer id without requiring an app restart.
Changes:
- Added a distributed-cache notification handler for
EntityContainerDeletedNotificationto invalidate element-container mappings on delete. - Added a
DistributedCacheExtensions.RefreshElementCache(IEnumerable<EntityContainer>)overload to emitElementCacheRefresherremove payloads for deleted element containers. - Added an integration regression test covering delete + recreate-under-same-key + children query behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
tests/Umbraco.Tests.Integration/Umbraco.Core/Cache/ElementContainerDeletedDistributedCacheNotificationHandlerTests.cs |
Integration regression test reproducing and validating the cache eviction behavior for deleted/recreated element containers. |
src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs |
Registers the new distributed-cache notification handler in core notifications. |
src/Umbraco.Core/Cache/NotificationHandlers/Implement/ElementContainerDeletedDistributedCacheNotificationHandler.cs |
New handler that routes element-container deletions into element cache refresh (remove) to evict IIdKeyMap mappings across servers. |
src/Umbraco.Core/Cache/DistributedCacheExtensions.cs |
Adds an overload to build/remove payloads for deleted element containers via ElementCacheRefresher. |
Routing container-delete invalidation through ElementCacheRefresher cleared the entire elements cache on every payload, so deleting a container triggered a full clear even though no element data changed (and a second clear on top of the ElementTreeChangeNotification refresh when the container held elements). Add a dedicated ElementContainerCacheRefresher whose only job is to evict the container's IIdKeyMap entry, and route EntityContainerDeletedNotification through it instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Description
#23072 reports a scenario found in testing with Umbraco Deploy, where the same container and element are transferred from one environment to another, deleted and then transferred again.
This leads to the an element container being deleted and recreated under the same fixed GUID, but it will be allocated a new integer Id.
In this situation the backoffice element tree's
childrenendpoint (GET /umbraco/management/api/v1/tree/element/children?parentId=<containerGuid>) returns no items for elements nested under an element container, even though the root endpoint reports the container withhasChildren: true. The tree only starts showing the children after the application is restarted.Root cause
The children endpoint resolves the container GUID to its integer id via
IIdKeyMap, then queriesWHERE ParentId == <resolvedId>. The failure happens because nothing has evicted the staleGUID → old-identry fromIIdKeyMapwhen the container was deleted.So the children query keeps resolving the container GUID to the old (now non-existent) id and returns nothing, until an app restart rebuilds the in-memory map.
Fix
A new notification handler,
ElementContainerDeletedDistributedCacheNotificationHandler, has been created and registered. It handlesEntityContainerDeletedNotificationand, for element containers, evicts the container's staleIIdKeyMapentry, via the distributed cache so it clears on every server in a load-balanced setup.The eviction runs through a dedicated
ElementContainerCacheRefresherwhose only job is to clear the container's id/key mapping. A container deletion changes no element data, so this deliberately avoids the broader invalidation inElementCacheRefresher(which clears the entire elements cache on every payload).Fixes #23072.
Testing
Automated
ElementContainerDeletedDistributedCacheNotificationHandlerTests(integration test) reproduces theexact mechanism:
GUID → idmapping inIIdKeyMap.returns the element.
Confirmed red before the fix (container GUID resolves to the old id;
total = 0) and green after.Manual
A small debug controller was used to reproduce and verify this end-to-end in a running site:
DebugElementTreeChildrenController.cs
Before the fix — the container GUID resolves to the old id and the tree returns nothing:
The container shows no children in the Library tree until the site is restarted.
After the fix — the GUID resolves to the recreated container's id and the element is returned:
The element appears in the Library tree without a restart ✅.