Skip to content

Cache: Invalidate element GUID-keyed cache on delete (closes #22911)#22940

Merged
AndyButland merged 1 commit into
release/18.0from
v18/bugfix/22911-ensure-deleted-elements-arent-findable-by-key-after-delete
May 21, 2026
Merged

Cache: Invalidate element GUID-keyed cache on delete (closes #22911)#22940
AndyButland merged 1 commit into
release/18.0from
v18/bugfix/22911-ensure-deleted-elements-arent-findable-by-key-after-delete

Conversation

@AndyButland

@AndyButland AndyButland commented May 21, 2026

Copy link
Copy Markdown
Contributor

Description

After moving an element to the recycle bin and emptying it, IElementService.GetById(Guid key) still returned the element until the site was restarted.

The EntityByGuidReadRepository used by element/content/media repositories stores its entries with the uRepoGuid_ prefix (via RepositoryCacheKeys.GetGuidKey<T>). ContentCacheRefresher and MediaCacheRefresher were updated to use that prefix when the GUID-keyed cache policy was introduced; ElementCacheRefresher was missed and still cleared with the old uRepo_ prefix (via RepositoryCacheKeys.GetKey<T, TId>), so the GUID-keyed entry was never invalidated.

The fix changes the GUID-key clearing in ElementCacheRefresher.Refresh from GetKey<IElement, Guid?> to GetGuidKey<IElement>, matching the pattern already used for documents and media.

Fixes #22911

Testing

Automated

I wasn't successful getting an integration test running that reliably shows the error before the fix and the resolution afterward.

Manual

Add this temporary debug controller (not committed) and run the site:

using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;

namespace Umbraco.Cms.Web.UI.Controllers;

public class DebugElementLookupController : Controller
{
    private readonly IElementService _elementService;

    public DebugElementLookupController(IElementService elementService) => _elementService = elementService;

    [HttpGet(\"/debug/element-lookup\")]
    public IActionResult Lookup([FromQuery] Guid key)
    {
        IElement? element = _elementService.GetById(key);
        return element is null
            ? Content(\$\"No element found for key '{key}'.\", \"text/plain\")
            : Content(\$\"Found element '{element.Name}' (id={element.Id}, trashed={element.Trashed}) for key '{key}'.\", \"text/plain\");
    }
}

Reproduction:

  1. In the backoffice, create a new element in the Library section and note its key.
  2. Move the element to the recycle bin.
  3. Empty the recycle bin.
  4. Browse to `/debug/element-lookup?key={key}`.

Expected response before the fix:

Found element 'My Element' (id=1234, trashed=True) for key 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'.

Expected response after the fix:

No element found for key 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'.

Copilot AI review requested due to automatic review settings May 21, 2026 13:44
@claude

claude Bot commented May 21, 2026

Copy link
Copy Markdown

Claude finished @AndyButland's task in 2m 28s —— View job


PR Review

Target: origin/release/18.0 · Based on commit: e2657358
· Skipped: 1 files out of 2 total (the element-publishing.workspace-context.ts change is from a separate commit already in release/18.0 and is not part of this PR)

Corrects the isolated cache invalidation key used when clearing a GUID-keyed element cache entry on delete. The old call used GetKey<IElement, Guid?> (prefix uRepo_IElement_), which never matched the actual stored key; the fix switches to GetGuidKey<IElement> (prefix uRepoGuid_IElement_), matching the key written by EntityByGuidReadRepository and aligning with the identical pattern already in ContentCacheRefresher and MediaCacheRefresher.


Suggestions

  • src/Umbraco.Core/Cache/Refreshers/Implement/ElementCacheRefresher.cs:131: No test specifically asserts that calling Refresh with TreeChangeTypes.Remove evicts the GUID-keyed isolated cache entry. The existing DistributedCacheRefresherTests only covers the elements memory cache, not the IsolatedCaches path. A unit test that verifies IAppPolicyCache.Clear is called with the uRepoGuid_IElement_ key on removal would prevent regression for this exact class of bug.

Approved

This looks good to be merged as-is. The two-line change is correct, precisely targeted, and directly mirrors the established pattern from ContentCacheRefresher (line 257) and MediaCacheRefresher (line 160). The added comment is justified — the uRepoGuid_ vs uRepo_ prefix distinction is genuinely non-obvious and was the root cause of the original bug.

@claude claude Bot added the area/backend label May 21, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes stale element lookups by GUID after an element is deleted by ensuring the GUID-keyed repository cache entry is invalidated by ElementCacheRefresher, aligning element cache invalidation with the existing content/media pattern.

Changes:

  • Update ElementCacheRefresher.Refresh to clear GUID-keyed repository cache entries using RepositoryCacheKeys.GetGuidKey<IElement>(...) (the uRepoGuid_ prefix) instead of the int-key prefix helper.

@AndyButland AndyButland merged commit 9276dd7 into release/18.0 May 21, 2026
29 checks passed
@AndyButland AndyButland deleted the v18/bugfix/22911-ensure-deleted-elements-arent-findable-by-key-after-delete branch May 21, 2026 14:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Deleted Elements are still findable by key after recycle bin is emptied.

3 participants