|
|
|
|
using System.Collections.Immutable;
|
|
|
|
|
using System.Globalization;
|
|
|
|
|
using Connected.Interop;
|
|
|
|
|
using Connected.Threading;
|
|
|
|
|
|
|
|
|
|
namespace Connected.Caching;
|
|
|
|
|
|
|
|
|
|
public abstract class StatefulCacheClient<TEntry, TKey> : CacheClient<TEntry, TKey>, IStatefulCacheClient<TEntry, TKey> where TEntry : class
|
|
|
|
|
{
|
|
|
|
|
public event CacheInvalidateHandler Invalidate;
|
|
|
|
|
protected StatefulCacheClient(ICachingService cachingService, string key) : base(cachingService, key)
|
|
|
|
|
{
|
|
|
|
|
Locker = new AsyncLockerSlim();
|
|
|
|
|
|
|
|
|
|
CachingService.Invalidating += OnInvalidate;
|
|
|
|
|
}
|
|
|
|
|
private AsyncLockerSlim? Locker { get; set; }
|
|
|
|
|
protected virtual InvalidateBehavior InvalidateBehavior { get; } = InvalidateBehavior.KeepSameInstance;
|
|
|
|
|
private bool Initialized { get; set; }
|
|
|
|
|
private async void OnInvalidate(CacheEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
if (string.Equals(e.Key, Key, StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
if (Initialized)
|
|
|
|
|
await OnInvalidate(TypeConversion.Convert<TKey>(e.Id, CultureInfo.InvariantCulture));
|
|
|
|
|
|
|
|
|
|
Invalidate?.Invoke(e);
|
|
|
|
|
|
|
|
|
|
e.Behavior = InvalidateBehavior;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
protected virtual async Task OnInvalidate(TKey id)
|
|
|
|
|
{
|
|
|
|
|
await Task.CompletedTask;
|
|
|
|
|
}
|
|
|
|
|
protected virtual async Task OnInitializing()
|
|
|
|
|
{
|
|
|
|
|
await Task.CompletedTask;
|
|
|
|
|
}
|
|
|
|
|
protected async Task Initialize()
|
|
|
|
|
{
|
|
|
|
|
if (Initialized || IsDisposed || Locker is null)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
await Locker.LockAsync(async () =>
|
|
|
|
|
{
|
|
|
|
|
if (Initialized || IsDisposed)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
await OnInitializing();
|
|
|
|
|
|
|
|
|
|
Initialized = true;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (Initialized)
|
|
|
|
|
await OnInitialized();
|
|
|
|
|
}
|
|
|
|
|
protected virtual async Task OnInitialized()
|
|
|
|
|
{
|
|
|
|
|
await Task.CompletedTask;
|
|
|
|
|
}
|
|
|
|
|
protected override async Task<ImmutableList<TEntry>?> All()
|
|
|
|
|
{
|
|
|
|
|
await Initialize();
|
|
|
|
|
|
|
|
|
|
return base.All().Result;
|
|
|
|
|
}
|
|
|
|
|
protected override async Task<TEntry?> First()
|
|
|
|
|
{
|
|
|
|
|
await Initialize();
|
|
|
|
|
|
|
|
|
|
return await base.First();
|
|
|
|
|
}
|
|
|
|
|
protected override async Task<TEntry?> Get(Func<TEntry, bool> predicate)
|
|
|
|
|
{
|
|
|
|
|
await Initialize();
|
|
|
|
|
|
|
|
|
|
return await base.Get(predicate);
|
|
|
|
|
}
|
|
|
|
|
protected override async Task<TEntry?> Get(TKey id)
|
|
|
|
|
{
|
|
|
|
|
await Initialize();
|
|
|
|
|
|
|
|
|
|
return await base.Get(id);
|
|
|
|
|
}
|
|
|
|
|
protected override async Task<TEntry?> Get(TKey id, Func<EntryOptions, Task<TEntry>> retrieve)
|
|
|
|
|
{
|
|
|
|
|
await Initialize();
|
|
|
|
|
|
|
|
|
|
return await base.Get(id, retrieve);
|
|
|
|
|
}
|
|
|
|
|
protected override async Task<ImmutableList<TEntry>?> Where(Func<TEntry, bool> predicate)
|
|
|
|
|
{
|
|
|
|
|
await Initialize();
|
|
|
|
|
|
|
|
|
|
return await base.Where(predicate);
|
|
|
|
|
}
|
|
|
|
|
protected override void OnDisposing()
|
|
|
|
|
{
|
|
|
|
|
if (Locker is not null)
|
|
|
|
|
{
|
|
|
|
|
Locker?.Dispose();
|
|
|
|
|
Locker = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override IEnumerator<TEntry> GetEnumerator()
|
|
|
|
|
{
|
|
|
|
|
AsyncUtils.RunSync(Initialize);
|
|
|
|
|
|
|
|
|
|
return base.GetEnumerator();
|
|
|
|
|
}
|
|
|
|
|
}
|