Repository Caches: Fix GUID read repository cache key collision causing GetAll failures (closes #21756)#21762
Merged
AndyButland merged 8 commits intomainfrom Feb 19, 2026
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes a warm-cache failure where GUID-keyed read repositories and their parent int-keyed repositories shared the same isolated cache key prefix, causing GetAll/GetMany cache count validation to misbehave and (for GUID repos) throw InvalidOperationException.
Changes:
- Added a GUID-specific repository cache key prefix (
uRepoGuid_{TypeName}_) viaRepositoryCacheKeys.GetGuidKey<T>(). - Introduced
GuidReadRepositoryCachePolicy<TEntity>and applied it to DataType/Document/Media GUID read repositories to prevent cache key collisions. - Updated Template GUID read repository to use
NoCacheRepositoryCachePolicyand added integration tests covering the warm-cache GUIDGetMany()scenario and DataTypeGetAllcaching behavior.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src/Umbraco.Core/Persistence/Repositories/RepositoryCacheKeys.cs |
Adds GUID-specific cache key prefix generation to avoid cross-repo prefix collisions. |
src/Umbraco.Infrastructure/Cache/GuidReadRepositoryCachePolicy.cs |
New cache policy for GUID-keyed read repositories using a distinct prefix. |
src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeRepository.cs |
GUID inner repo now uses GuidReadRepositoryCachePolicy + GUID-prefixed cache keys. |
src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs |
Same as above for document GUID inner repo. |
src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs |
Same as above for media GUID inner repo. |
src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs |
Removes GUID-key cache population/clearing and uses no-cache policy for GUID inner repo to avoid stale GUID entries. |
tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DataTypeRepositoryTest.cs |
Adds warm-cache regression tests for GUID GetMany() and cached GetMany()/GetAll behavior. |
tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs |
Adds warm-cache regression test for GUID GetMany(). |
tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaRepositoryTest.cs |
Adds warm-cache regression test for GUID GetMany(). |
tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs |
Adds warm-cache regression test for GUID GetMany(). |
tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/DataTypeServiceTests.cs |
Updates tests to use async create APIs for templates/content types and adjusts obsolete API warning suppression. |
src/Umbraco.Infrastructure/Cache/GuidReadRepositoryCachePolicy.cs
Outdated
Show resolved
Hide resolved
tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/DataTypeServiceTests.cs
Show resolved
Hide resolved
tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/DataTypeServiceTests.cs
Show resolved
Hide resolved
GetAll failures (closes #21756)
nikolajlauridsen
approved these changes
Feb 17, 2026
Contributor
nikolajlauridsen
left a comment
There was a problem hiding this comment.
Looks good, only have one minor concern 😄
src/Umbraco.Core/Cache/Refreshers/Implement/DataTypeCacheRefresher.cs
Outdated
Show resolved
Hide resolved
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Fixes #21756 —
DataTypeService.GetAllAsync()(and equivalent calls for Document, Media, and Template) throwsInvalidOperationExceptionon the second invocation when the cache is warm.Root Cause
GUID-keyed read repositories (e.g.
DataTypeByGuidReadRepository) and their parent int-keyed repositories both resolve to the sameIAppPolicyCacheviaIsolatedCaches.GetOrCreate<TEntity>(), and both used the same cache key prefix ("uRepo_{TypeName}_"). WhenDefaultRepositoryCachePolicy.GetAllruns its count validation, it finds both int-keyed and GUID-keyed entries under the same prefix, doubling the expected count. This causes a count mismatch, which falls through toPerformGetAll→GetBaseQuery(isCount: true)— a method that throwsInvalidOperationException("This method won't be implemented.")on the inner GUID read repositories.As well as throwing this exception, investigating and fixing this bug also revealed that the int-keyed
GetAllpath suffered a performance issue: GUID entries inflated the prefix-based cache count, causing count validation to always fail even after a successfulGetAllpopulated the cache. This forced a full database re-query on every subsequentGetAllcall.Finally, I also found that we were missing some specific invalidations of the GUID caches.
Fixes
Introduced
GuidReadRepositoryCachePolicy<TEntity>with a distinct cache key prefix ("uRepoGuid_{TypeName}_") so GUID-keyed entries never interfere with the int-keyed repository's prefix search and count validation.With the separate prefix, count validation now passes correctly and data is served from cache (only a single
SELECT COUNT(*)validation query is executed).The Template repository fix used a different approach, which was simply to remove the GUID read repository. The
TemplateByGuidReadRepositorywas introduced in #21280 to mirror the GUID caching pattern used by Document and Media repositories. However, unlike those repositories which useDefaultRepositoryCachePolicy(caching individual entities bykey),TemplateRepositoryusesFullDataSetRepositoryCachePolicywhich already caches all templates as a single collection. So this update was unnecessary as the GUID lookup methods could simply filter the already-cached full dataset viaGetMany(). Removing it both fixes the cache collision bug and simplifies the code to match what the architecture already provides.The missing invalidations of the GUID caches where added.
Refactoring
This PR also includes a minor refactor in moving all the
TimeSpan.FromMinutes(5)into a constant where the time can be managed in one place.Testing
New integration tests have been added to verify:
For manual testing the following surface controller can be used. Access via
/umbraco/surface/DataTypeCacheBug/Test:Before the fix, this will show the exception being thrown. After the fix, it displays the expected result.