You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Connected.Framework/Connected.Caching/CacheContext.cs

220 lines
5.2 KiB

2 years ago
using System.Collections.Immutable;
using System.Reflection;
using Connected.Interop;
using Connected.ServiceModel.Transactions;
namespace Connected.Caching;
internal class CacheContext : Cache, ICacheContext
{
public CacheContext(ICachingService cachingService, ITransactionContext transactionContext)
{
CachingService = cachingService;
TransactionContext = transactionContext;
TransactionContext.StateChanged += OnTransactionContextStateChanged;
}
private void OnTransactionContextStateChanged(object? sender, EventArgs e)
{
if (TransactionContext.State == MiddlewareTransactionState.Committing)
Flush();
}
private ICachingService CachingService { get; }
private ITransactionContext TransactionContext { get; }
public override bool Exists(string key)
{
return base.Exists(key) || (CachingService is not null && CachingService.Exists(key));
}
public override bool IsEmpty(string key)
{
return base.IsEmpty(key) || (CachingService is not null && CachingService.IsEmpty(key));
}
public override ImmutableList<T>? All<T>(string key)
{
return Merge(base.All<T>(key), CachingService?.All<T>(key));
}
public override async Task<T> Get<T>(string key, object id, Func<EntryOptions, Task<T>>? retrieve)
{
if (!TransactionContext.IsDirty)
{
if (retrieve is null)
return default;
return await CachingService.Get(key, id, retrieve);
}
return await base.Get(key, id, (f) =>
{
var shared = CachingService.Get<T>(key, id);
if (shared is not null)
return Task.FromResult(shared);
if (retrieve is null)
return default;
return retrieve(f);
});
}
public override async Task<T> Get<T>(string key, Func<T, bool> predicate, Func<EntryOptions, Task<T>>? retrieve)
{
if (!TransactionContext.IsDirty)
{
if (retrieve is null)
return default;
return await CachingService.Get(key, predicate, retrieve);
}
return await base.Get(key, predicate, (f) =>
{
var shared = CachingService.Get(key, predicate);
if (shared is not null)
return Task.FromResult(shared);
return retrieve(f);
});
}
public override T Get<T>(string key, object id)
{
var contextItem = base.Get<T>(key, id);
if (contextItem is not null)
return contextItem;
return CachingService.Get<T>(key, id);
}
public override T Get<T>(string key, Func<T, bool> predicate)
{
var contextItem = base.Get<T>(key, predicate);
if (contextItem is not null)
return contextItem;
return CachingService.Get(key, predicate);
}
public override async Task Clear(string key)
{
await base.Clear(key);
await CachingService.Clear(key);
}
public override T First<T>(string key)
{
if (base.First<T>(key) is T result)
return result;
return CachingService.First<T>(key);
}
public override ImmutableList<T> Where<T>(string key, Func<T, bool> predicate)
{
return Merge(base.Where(key, predicate), CachingService.Where(key, predicate));
}
public override T Set<T>(string key, object id, T instance)
{
if (!TransactionContext.IsDirty)
return CachingService.Set(key, id, instance);
return base.Set(key, id, instance);
}
public override T Set<T>(string key, object id, T instance, TimeSpan duration)
{
if (!TransactionContext.IsDirty)
return CachingService.Set(key, id, instance, duration);
return base.Set(key, id, instance, duration);
}
public override T Set<T>(string key, object id, T instance, TimeSpan duration, bool slidingExpiration)
{
if (!TransactionContext.IsDirty)
return CachingService.Set(key, id, instance, duration, slidingExpiration);
return base.Set(key, id, instance, duration, slidingExpiration);
}
public override async Task Remove(string key, object id)
{
await base.Remove(key, id);
await CachingService.Remove(key, id);
}
public override async Task<ImmutableList<string>?> Remove<T>(string key, Func<T, bool> predicate)
{
var local = await base.Remove(key, predicate);
var shared = await CachingService.Remove(key, predicate);
if (local is not null && shared is not null)
return local.AddRange(shared);
return local is not null ? local : shared;
}
public void Flush()
{
CachingService.Merge(this);
}
private static ImmutableList<T>? Merge<T>(ImmutableList<T>? contextItems, ImmutableList<T>? sharedItems)
{
if (contextItems is null)
return sharedItems;
var result = new List<T>(contextItems);
foreach (var sharedItem in sharedItems)
{
if (sharedItem is null)
continue;
if (CachingExtensions.GetCacheKeyProperty(sharedItem) is not PropertyInfo cacheProperty)
{
//Q: should we compare every property and add only if not matched?
contextItems.Add(sharedItem);
continue;
}
if (FindExisting(cacheProperty.GetValue(sharedItems), contextItems) is null)
result.Add(sharedItem);
}
return result.ToImmutableList();
}
private static T? FindExisting<T>(object value, ImmutableList<T> items)
{
if (items is null || items.IsEmpty)
return default;
if (CachingExtensions.GetCacheKeyProperty(items[0]) is not PropertyInfo cacheProperty)
return default;
foreach (var item in items)
{
var id = cacheProperty.GetValue(item);
if (TypeComparer.Compare(id, value))
return item;
}
return default;
}
protected override async Task OnInvalidate(CacheEventArgs e)
{
await CachingService.Invalidate(e.Key, e.Id);
}
}