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? All(string key) { return Merge(base.All(key), CachingService?.All(key)); } public override async Task Get(string key, object id, Func>? 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(key, id); if (shared is not null) return Task.FromResult(shared); if (retrieve is null) return default; return retrieve(f); }); } public override async Task Get(string key, Func predicate, Func>? 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(string key, object id) { var contextItem = base.Get(key, id); if (contextItem is not null) return contextItem; return CachingService.Get(key, id); } public override T Get(string key, Func predicate) { var contextItem = base.Get(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(string key) { if (base.First(key) is T result) return result; return CachingService.First(key); } public override ImmutableList Where(string key, Func predicate) { return Merge(base.Where(key, predicate), CachingService.Where(key, predicate)); } public override T Set(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(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(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?> Remove(string key, Func 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? Merge(ImmutableList? contextItems, ImmutableList? sharedItems) { if (contextItems is null) return sharedItems; var result = new List(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(object value, ImmutableList 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); } }