namespace GrossesMitainesAPI.Services; #region Dependencies using GrossesMitainesAPI.Data; using GrossesMitainesAPI.Models; using Microsoft.EntityFrameworkCore; #endregion public class DatabaseCacheService { #region DI private readonly IServiceScopeFactory _contextFactory; // https://entityframeworkcore.com/knowledge-base/51939451/how-to-use-a-database-context-in-a-singleton-service- private readonly ILogger _logger; #endregion #region Fields private Product[] _cache = new Product[1]; private Dictionary _hits = new(); private bool _ok = false, _needUpd = true; private PeriodicTimer _timer = new PeriodicTimer(TimeSpan.FromSeconds(10)); #endregion #region Ctor public DatabaseCacheService(ILogger logger, IServiceScopeFactory scopeFactory) { _contextFactory = scopeFactory; _logger = logger; _ok = UpdateCache(); _needUpd = !_ok; UpdateJob(); } #endregion #region Internal Methods private async void UpdateJob() { while (await _timer.WaitForNextTickAsync()) { if (_needUpd) { _ok = UpdateCache(); _needUpd = !_ok; } if (_hits.Count > 0 && _ok) UpdateMetrics(); // les updates de metrics ne déclencheront pas d'update de cache // puisque les clients ne voient pas les métriques. } } private bool UpdateCache() { try { Product[] prods; using (var scope = _contextFactory.CreateScope()) { var db = scope.ServiceProvider.GetRequiredService(); prods = db.Products.ToArray(); } lock (_cache) { _cache = prods; } } catch (Exception e) { _logger.LogError(e, "Erreur de mise à jour de cache."); return false; } return true; } private bool UpdateMetrics() { try { Dictionary hits; lock (_hits) { hits = new(_hits); _hits.Clear(); } List ids = hits.Keys.ToList(); using (var scope = _contextFactory.CreateScope()) { var db = scope.ServiceProvider.GetRequiredService(); List lst = db.Products.Where(x => ids.Contains((uint)x.Id)).ToList(); foreach (var x in hits) lst.First(x => x.Id == x.Id).Hits += x.Value; db.UpdateRange(lst); db.SaveChanges(); } } catch (Exception e) { _logger.LogError(e, "Erreur de mise à jour de cache."); return false; } return true; } #endregion #region Public Methods public bool isOk() { return _ok; } public void askForRefresh() { _needUpd = true; } public void addHit(uint id) { lock (_hits) { if (_hits.ContainsKey(id)) _hits[id] = _hits[id] + 1; else _hits[id] = 1; } } public Product[]? GetCacheCopy() { if (!_ok) return null; Product[] copy; try { lock (_cache) { copy = new Product[_cache.Length]; _cache.CopyTo(copy, 0); } } catch (Exception e) { _logger.LogError(e, "Erreur de copie de cache."); return null; } return copy; } public IQueryable queryCache() { if (!_ok) return null; try { return _cache.AsQueryable(); } catch (Exception e) { _logger.LogError(e, "Erreur de cache."); return null; } } #endregion }