using Data.Abstractions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Data.Helpers { public class LookupCache : ILookupCache where T : class, IUniqueId { private readonly ILogger _logger; private readonly IDatabaseContextFactory _contextFactory; private Dictionary _cachedItems; private readonly SemaphoreSlim _onOperation = new SemaphoreSlim(1, 1); public LookupCache(ILogger> logger, IDatabaseContextFactory contextFactory) { _logger = logger; _contextFactory = contextFactory; } public async Task AddAsync(T item) { await _onOperation.WaitAsync(); T existingItem = null; if (_cachedItems.ContainsKey(item.Id)) { existingItem = _cachedItems[item.Id]; } if (existingItem != null) { _logger.LogDebug("Cached item already added for {type} {id} {value}", typeof(T).Name, item.Id, item.Value); _onOperation.Release(); return existingItem; } try { _logger.LogDebug("Adding new {type} with {id} {value}", typeof(T).Name, item.Id, item.Value); await using var context = _contextFactory.CreateContext(); context.Set().Add(item); await context.SaveChangesAsync(); _cachedItems.Add(item.Id, item); return item; } catch (Exception ex) { _logger.LogError(ex, "Could not add item to cache for {type}", typeof(T).Name); throw new Exception("Could not add item to cache"); } finally { if (_onOperation.CurrentCount == 0) { _onOperation.Release(); } } } public async Task FirstAsync(Func query) { await _onOperation.WaitAsync(); try { var cachedResult = _cachedItems.Values.Where(query); if (cachedResult.Any()) { return cachedResult.FirstOrDefault(); } } catch { } finally { if (_onOperation.CurrentCount == 0) { _onOperation.Release(1); } } return null; } public IEnumerable GetAll() { return _cachedItems.Values; } public async Task InitializeAsync() { try { await using var context = _contextFactory.CreateContext(); _cachedItems = await context.Set().ToDictionaryAsync(item => item.Id); } catch (Exception ex) { _logger.LogError(ex, "Could not initialize caching for {cacheType}", typeof(T).Name); } } } }