Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache invalidation with partial key knowledge #87

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions LazyCache.UnitTests/CachingServiceMemoryCacheProviderTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
Expand Down Expand Up @@ -755,5 +756,33 @@ public void RemovedItemCannotBeRetrievedFromCache()
sut.Remove(TestKey);
Assert.Null(sut.Get<object>(TestKey));
}

[Test]
public void RemovedItemWithCompositeKeyCannotBeRetrievedFromCache()
{
var internalTestKey1 = TestKey + ".1";
var internalTestKey2 = TestKey + ".2";
var internalTestKey3 = "1." + TestKey;

IEnumerable<string> CompositeKeyTest(IEnumerable<string> 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<object>(internalTestKey1));
Assert.NotNull(sut.Get<object>(internalTestKey2));
Assert.NotNull(sut.Get<object>(internalTestKey3));

sut.Remove(CompositeKeyTest);
Assert.Null(sut.Get<object>(internalTestKey1));
Assert.Null(sut.Get<object>(internalTestKey2));
Assert.NotNull(sut.Get<object>(internalTestKey3));

sut.Remove(internalTestKey3);
Assert.Null(sut.Get<object>(internalTestKey3));
}
}
}
7 changes: 7 additions & 0 deletions LazyCache/CachingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,13 @@ public virtual void Remove(string key)
CacheProvider.Remove(key);
}

public virtual void Remove(Func<IEnumerable<string>, IEnumerable<string>> keyPredicate)
{
if (keyPredicate == null)
throw new ArgumentNullException(nameof(keyPredicate));
CacheProvider.Remove(keyPredicate);
}

public virtual ICacheProvider CacheProvider => cacheProvider.Value;

public virtual async Task<T> GetOrAddAsync<T>(string key, Func<ICacheEntry, Task<T>> addItemFactory)
Expand Down
3 changes: 3 additions & 0 deletions LazyCache/IAppCache.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;

Expand All @@ -24,5 +25,7 @@ public interface IAppCache
Task<T> GetOrAddAsync<T>(string key, Func<ICacheEntry, Task<T>> addItemFactory);

void Remove(string key);

void Remove(Func<IEnumerable<string>, IEnumerable<string>> keyPredicate);
}
}
2 changes: 2 additions & 0 deletions LazyCache/ICacheProvider.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;

Expand All @@ -10,6 +11,7 @@ public interface ICacheProvider : IDisposable
object Get(string key);
object GetOrCreate<T>(string key, Func<ICacheEntry, T> func);
void Remove(string key);
void Remove(Func<IEnumerable<string>, IEnumerable<string>> keyPredicate);
Task<T> GetOrCreateAsync<T>(string key, Func<ICacheEntry, Task<T>> func);
}
}
5 changes: 5 additions & 0 deletions LazyCache/Mocks/MockCacheProvider.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;

Expand All @@ -24,6 +25,10 @@ public void Remove(string key)
{
}

public void Remove(Func<IEnumerable<string>, IEnumerable<string>> keyPredicate)
{
}

public Task<T> GetOrCreateAsync<T>(string key, Func<ICacheEntry, Task<T>> func)
{
return func(null);
Expand Down
5 changes: 5 additions & 0 deletions LazyCache/Mocks/MockCachingService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;

Expand Down Expand Up @@ -27,6 +28,10 @@ public void Remove(string key)
{
}

public void Remove(Func<IEnumerable<string>, IEnumerable<string>> keyPredicate)
{
}

public Task<T> GetOrAddAsync<T>(string key, Func<ICacheEntry, Task<T>> addItemFactory)
{
return addItemFactory(new MockCacheEntry(key));
Expand Down
31 changes: 30 additions & 1 deletion LazyCache/Providers/MemoryCacheProvider.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;

Expand All @@ -7,39 +9,66 @@ namespace LazyCache.Providers
public class MemoryCacheProvider : ICacheProvider
{
internal readonly IMemoryCache cache;
private List<string> _keys;

public MemoryCacheProvider(IMemoryCache cache)
{
this.cache = cache;
_keys = new List<string>();
}

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<T>(string key, Func<ICacheEntry, T> 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<string>, IEnumerable<string>> 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<T> GetOrCreateAsync<T>(string key, Func<ICacheEntry, Task<T>> factory)
{
if (!_keys.Contains(key))
_keys.Add(key);

return cache.GetOrCreateAsync(key, factory);
}

public void Dispose()
{
_keys = null;
cache?.Dispose();
}
}
Expand Down