From bc6a867bf8d32768fb12029f067f8a1afb83dce0 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Mon, 19 Aug 2019 19:55:52 -0500 Subject: [PATCH] Added functionality to support the removal of keys based on a predicate function against a key comparison. --- .../CachingServiceMemoryCacheProviderTests.cs | 29 +++++++++++++++++ LazyCache/CachingService.cs | 7 +++++ LazyCache/IAppCache.cs | 3 ++ LazyCache/ICacheProvider.cs | 2 ++ LazyCache/Mocks/MockCacheProvider.cs | 5 +++ LazyCache/Mocks/MockCachingService.cs | 5 +++ LazyCache/Providers/MemoryCacheProvider.cs | 31 ++++++++++++++++++- 7 files changed, 81 insertions(+), 1 deletion(-) diff --git a/LazyCache.UnitTests/CachingServiceMemoryCacheProviderTests.cs b/LazyCache.UnitTests/CachingServiceMemoryCacheProviderTests.cs index 85e5e8b..51958b4 100644 --- a/LazyCache.UnitTests/CachingServiceMemoryCacheProviderTests.cs +++ b/LazyCache.UnitTests/CachingServiceMemoryCacheProviderTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using FluentAssertions; @@ -755,5 +756,33 @@ public void RemovedItemCannotBeRetrievedFromCache() sut.Remove(TestKey); Assert.Null(sut.Get(TestKey)); } + + [Test] + public void RemovedItemWithCompositeKeyCannotBeRetrievedFromCache() + { + var internalTestKey1 = TestKey + ".1"; + var internalTestKey2 = TestKey + ".2"; + var internalTestKey3 = "1." + TestKey; + + IEnumerable CompositeKeyTest(IEnumerable keys) + { + return keys.Where(a => a.StartsWith(TestKey)); + } + + sut.Add(internalTestKey1, new object()); + sut.Add(internalTestKey2, new object()); + sut.Add(internalTestKey3, new object()); + Assert.NotNull(sut.Get(internalTestKey1)); + Assert.NotNull(sut.Get(internalTestKey2)); + Assert.NotNull(sut.Get(internalTestKey3)); + + sut.Remove(CompositeKeyTest); + Assert.Null(sut.Get(internalTestKey1)); + Assert.Null(sut.Get(internalTestKey2)); + Assert.NotNull(sut.Get(internalTestKey3)); + + sut.Remove(internalTestKey3); + Assert.Null(sut.Get(internalTestKey3)); + } } } \ No newline at end of file diff --git a/LazyCache/CachingService.cs b/LazyCache/CachingService.cs index 1bd5305..5b6f700 100644 --- a/LazyCache/CachingService.cs +++ b/LazyCache/CachingService.cs @@ -122,6 +122,13 @@ public virtual void Remove(string key) CacheProvider.Remove(key); } + public virtual void Remove(Func, IEnumerable> keyPredicate) + { + if (keyPredicate == null) + throw new ArgumentNullException(nameof(keyPredicate)); + CacheProvider.Remove(keyPredicate); + } + public virtual ICacheProvider CacheProvider => cacheProvider.Value; public virtual async Task GetOrAddAsync(string key, Func> addItemFactory) diff --git a/LazyCache/IAppCache.cs b/LazyCache/IAppCache.cs index b474cbe..b6d7d9e 100644 --- a/LazyCache/IAppCache.cs +++ b/LazyCache/IAppCache.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Memory; @@ -24,5 +25,7 @@ public interface IAppCache Task GetOrAddAsync(string key, Func> addItemFactory); void Remove(string key); + + void Remove(Func, IEnumerable> keyPredicate); } } \ No newline at end of file diff --git a/LazyCache/ICacheProvider.cs b/LazyCache/ICacheProvider.cs index 2007416..04d4c6b 100644 --- a/LazyCache/ICacheProvider.cs +++ b/LazyCache/ICacheProvider.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Memory; @@ -10,6 +11,7 @@ public interface ICacheProvider : IDisposable object Get(string key); object GetOrCreate(string key, Func func); void Remove(string key); + void Remove(Func, IEnumerable> keyPredicate); Task GetOrCreateAsync(string key, Func> func); } } \ No newline at end of file diff --git a/LazyCache/Mocks/MockCacheProvider.cs b/LazyCache/Mocks/MockCacheProvider.cs index c7463e7..1468987 100644 --- a/LazyCache/Mocks/MockCacheProvider.cs +++ b/LazyCache/Mocks/MockCacheProvider.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Memory; @@ -24,6 +25,10 @@ public void Remove(string key) { } + public void Remove(Func, IEnumerable> keyPredicate) + { + } + public Task GetOrCreateAsync(string key, Func> func) { return func(null); diff --git a/LazyCache/Mocks/MockCachingService.cs b/LazyCache/Mocks/MockCachingService.cs index 51b92fd..8bbb263 100644 --- a/LazyCache/Mocks/MockCachingService.cs +++ b/LazyCache/Mocks/MockCachingService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Memory; @@ -27,6 +28,10 @@ public void Remove(string key) { } + public void Remove(Func, IEnumerable> keyPredicate) + { + } + public Task GetOrAddAsync(string key, Func> addItemFactory) { return addItemFactory(new MockCacheEntry(key)); diff --git a/LazyCache/Providers/MemoryCacheProvider.cs b/LazyCache/Providers/MemoryCacheProvider.cs index 9bdfdc9..53a54f2 100644 --- a/LazyCache/Providers/MemoryCacheProvider.cs +++ b/LazyCache/Providers/MemoryCacheProvider.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Memory; @@ -7,39 +9,66 @@ namespace LazyCache.Providers public class MemoryCacheProvider : ICacheProvider { internal readonly IMemoryCache cache; + private List _keys; public MemoryCacheProvider(IMemoryCache cache) { this.cache = cache; + _keys = new List(); } public void Set(string key, object item, MemoryCacheEntryOptions policy) { + _keys.Add(key); cache.Set(key, item, policy); } public object Get(string key) { - return cache.Get(key); + var attemptedResult = cache.TryGetValue(key, out var result); + + //Remove the key from the key cache if it exists and this didn't return a result + if (attemptedResult == false && _keys.Contains(key)) + _keys.Remove(key); + + return result; } public object GetOrCreate(string key, Func factory) { + if (!_keys.Contains(key)) + _keys.Add(key); + return cache.GetOrCreate(key, factory); } public void Remove(string key) { + _keys.Remove(key); cache.Remove(key); } + public void Remove(Func, IEnumerable> keyPredicate) + { + //Search the keys for any matches to the predicate + var keyMatches = keyPredicate(_keys).ToList(); + + //Remove each of the matches + foreach (var match in keyMatches) + Remove(match); + } + public Task GetOrCreateAsync(string key, Func> factory) { + if (!_keys.Contains(key)) + _keys.Add(key); + return cache.GetOrCreateAsync(key, factory); } public void Dispose() { + _keys = null; cache?.Dispose(); } }