Skip to content

Commit 6620aca

Browse files
callumbwhyteAndyButlandMigaroez
authored
Cache null dictionary values by key (#15576)
* Add CacheNullValues option to RepositoryCachePolicy * Cache null values in DictionaryByKeyRepository * Fixed issue with nullable reference. * Updated logic for caching of null values. * Update src/Umbraco.Infrastructure/Cache/DefaultRepositoryCachePolicy.cs Co-authored-by: Sven Geusens <[email protected]> * Made the NullValueRepresentation overwritable in a generic manner * Improve generic NullValueCachePolicyResolver * Revert Commits and clarify logic with comment This reverts commit 8befb43 "Improve generic NullValueCachePolicyResolver" Also reverts 8adf0a2 - Made the NullValueRepresentation overwritable in a generic manner And 8adf0a2 - Made the NullValueRepresentation overwritable in a generic manner * Update src/Umbraco.Infrastructure/Cache/DefaultRepositoryCachePolicy.cs --------- Co-authored-by: Andy Butland <[email protected]> Co-authored-by: Sven Geusens <[email protected]> Co-authored-by: Sven Geusens <[email protected]>
1 parent b4a9dc0 commit 6620aca

File tree

3 files changed

+45
-11
lines changed

3 files changed

+45
-11
lines changed

src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public class RepositoryCachePolicyOptions
1111
public RepositoryCachePolicyOptions(Func<int> performCount)
1212
{
1313
PerformCount = performCount;
14+
CacheNullValues = false;
1415
GetAllCacheValidateCount = true;
1516
GetAllCacheAllowZeroCount = false;
1617
}
@@ -21,6 +22,7 @@ public RepositoryCachePolicyOptions(Func<int> performCount)
2122
public RepositoryCachePolicyOptions()
2223
{
2324
PerformCount = null;
25+
CacheNullValues = false;
2426
GetAllCacheValidateCount = false;
2527
GetAllCacheAllowZeroCount = false;
2628
}
@@ -30,6 +32,11 @@ public RepositoryCachePolicyOptions()
3032
/// </summary>
3133
public Func<int>? PerformCount { get; set; }
3234

35+
/// <summary>
36+
/// True if the Get method will cache null results so that the db is not hit for repeated lookups
37+
/// </summary>
38+
public bool CacheNullValues { get; set; }
39+
3340
/// <summary>
3441
/// True/false as to validate the total item count when all items are returned from cache, the default is true but this
3542
/// means that a db lookup will occur - though that lookup will probably be significantly less expensive than the

src/Umbraco.Infrastructure/Cache/DefaultRepositoryCachePolicy.cs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ public class DefaultRepositoryCachePolicy<TEntity, TId> : RepositoryCachePolicyB
2424
private static readonly TEntity[] _emptyEntities = new TEntity[0]; // const
2525
private readonly RepositoryCachePolicyOptions _options;
2626

27+
private const string NullRepresentationInCache = "*NULL*";
28+
2729
public DefaultRepositoryCachePolicy(IAppPolicyCache cache, IScopeAccessor scopeAccessor, RepositoryCachePolicyOptions options)
2830
: base(cache, scopeAccessor) =>
2931
_options = options ?? throw new ArgumentNullException(nameof(options));
@@ -116,6 +118,7 @@ public override void Delete(TEntity entity, Action<TEntity> persistDeleted)
116118
{
117119
// whatever happens, clear the cache
118120
var cacheKey = GetEntityCacheKey(entity.Id);
121+
119122
Cache.Clear(cacheKey);
120123

121124
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
@@ -127,20 +130,36 @@ public override void Delete(TEntity entity, Action<TEntity> persistDeleted)
127130
public override TEntity? Get(TId? id, Func<TId?, TEntity?> performGet, Func<TId[]?, IEnumerable<TEntity>?> performGetAll)
128131
{
129132
var cacheKey = GetEntityCacheKey(id);
133+
130134
TEntity? fromCache = Cache.GetCacheItem<TEntity>(cacheKey);
131135

132-
// if found in cache then return else fetch and cache
133-
if (fromCache != null)
136+
// If found in cache then return immediately.
137+
if (fromCache is not null)
134138
{
135139
return fromCache;
136140
}
137141

142+
// Because TEntity can never be a string, we will never be in a position where the proxy value collides withs a real value.
143+
// Therefore this point can only be reached if there is a proxy null value => becomes null when cast to TEntity above OR the item simply does not exist.
144+
// If we've cached a "null" value, return null.
145+
if (_options.CacheNullValues && Cache.GetCacheItem<string>(cacheKey) == NullRepresentationInCache)
146+
{
147+
return null;
148+
}
149+
150+
// Otherwise go to the database to retrieve.
138151
TEntity? entity = performGet(id);
139152

140153
if (entity != null && entity.HasIdentity)
141154
{
155+
// If we've found an identified entity, cache it for subsequent retrieval.
142156
InsertEntity(cacheKey, entity);
143157
}
158+
else if (entity is null && _options.CacheNullValues)
159+
{
160+
// If we've not found an entity, and we're caching null values, cache a "null" value.
161+
InsertNull(cacheKey);
162+
}
144163

145164
return entity;
146165
}
@@ -248,6 +267,15 @@ protected string GetEntityCacheKey(TId? id)
248267
protected virtual void InsertEntity(string cacheKey, TEntity entity)
249268
=> Cache.Insert(cacheKey, () => entity, TimeSpan.FromMinutes(5), true);
250269

270+
protected virtual void InsertNull(string cacheKey)
271+
{
272+
// We can't actually cache a null value, as in doing so wouldn't be able to distinguish between
273+
// a value that does exist but isn't yet cached, or a value that has been explicitly cached with a null value.
274+
// Both would return null when we retrieve from the cache and we couldn't distinguish between the two.
275+
// So we cache a special value that represents null, and then we can check for that value when we retrieve from the cache.
276+
Cache.Insert(cacheKey, () => NullRepresentationInCache, TimeSpan.FromMinutes(5), true);
277+
}
278+
251279
protected virtual void InsertEntities(TId[]? ids, TEntity[]? entities)
252280
{
253281
if (ids?.Length == 0 && entities?.Length == 0 && _options.GetAllCacheAllowZeroCount)

src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,10 @@ protected override IRepositoryCachePolicy<IDictionaryItem, int> CreateCachePolic
102102
var options = new RepositoryCachePolicyOptions
103103
{
104104
// allow zero to be cached
105-
GetAllCacheAllowZeroCount = true,
105+
GetAllCacheAllowZeroCount = true
106106
};
107107

108-
return new SingleItemsOnlyRepositoryCachePolicy<IDictionaryItem, int>(GlobalIsolatedCache, ScopeAccessor,
109-
options);
108+
return new SingleItemsOnlyRepositoryCachePolicy<IDictionaryItem, int>(GlobalIsolatedCache, ScopeAccessor, options);
110109
}
111110

112111
protected IDictionaryItem ConvertFromDto(DictionaryDto dto)
@@ -190,11 +189,10 @@ protected override IRepositoryCachePolicy<IDictionaryItem, Guid> CreateCachePoli
190189
var options = new RepositoryCachePolicyOptions
191190
{
192191
// allow zero to be cached
193-
GetAllCacheAllowZeroCount = true,
192+
GetAllCacheAllowZeroCount = true
194193
};
195194

196-
return new SingleItemsOnlyRepositoryCachePolicy<IDictionaryItem, Guid>(GlobalIsolatedCache, ScopeAccessor,
197-
options);
195+
return new SingleItemsOnlyRepositoryCachePolicy<IDictionaryItem, Guid>(GlobalIsolatedCache, ScopeAccessor, options);
198196
}
199197
}
200198

@@ -228,12 +226,13 @@ protected override IRepositoryCachePolicy<IDictionaryItem, string> CreateCachePo
228226
{
229227
var options = new RepositoryCachePolicyOptions
230228
{
229+
// allow null to be cached
230+
CacheNullValues = true,
231231
// allow zero to be cached
232-
GetAllCacheAllowZeroCount = true,
232+
GetAllCacheAllowZeroCount = true
233233
};
234234

235-
return new SingleItemsOnlyRepositoryCachePolicy<IDictionaryItem, string>(GlobalIsolatedCache, ScopeAccessor,
236-
options);
235+
return new SingleItemsOnlyRepositoryCachePolicy<IDictionaryItem, string>(GlobalIsolatedCache, ScopeAccessor, options);
237236
}
238237
}
239238

0 commit comments

Comments
 (0)