diff --git a/GrossesMitaines/GrossesMitainesAPI/Controllers/InventoryController.cs b/GrossesMitaines/GrossesMitainesAPI/Controllers/InventoryController.cs index 5869410..7a0d221 100644 --- a/GrossesMitaines/GrossesMitainesAPI/Controllers/InventoryController.cs +++ b/GrossesMitaines/GrossesMitainesAPI/Controllers/InventoryController.cs @@ -4,6 +4,7 @@ using System.Linq; using GrossesMitainesAPI.Data; using Microsoft.Extensions.Logging; using Microsoft.AspNetCore.Cors; +using GrossesMitainesAPI.Services; namespace GrossesMitainesAPI.Controllers; @@ -12,10 +13,12 @@ namespace GrossesMitainesAPI.Controllers; public class InventoryController : Controller { private readonly ILogger _logger; private readonly InventoryContext _context; + private readonly DatabaseCacheService _cache; - public InventoryController(ILogger logger, InventoryContext context) { + public InventoryController(ILogger logger, InventoryContext context, DatabaseCacheService cache) { _context = context; _logger = logger; + _cache = cache; } [EnableCors("_myAllowSpecificOrigins")] @@ -23,7 +26,11 @@ public class InventoryController : Controller { public IEnumerable Get(int? lastId, string? order, string? filterPrice, string? filterState, bool? all) { const int AMOUNT = 5; - var ret = _context.Products.AsQueryable(); + IQueryable ret; + + if (_cache.isOk()) + ret = _cache.GetCacheCopy().AsQueryable(); + else ret = _context.Products.AsQueryable(); switch (filterPrice) { case "PriceUnder20": diff --git a/GrossesMitaines/GrossesMitainesAPI/Controllers/ProductController.cs b/GrossesMitaines/GrossesMitainesAPI/Controllers/ProductController.cs index 9dfc87c..bcd5a71 100644 --- a/GrossesMitaines/GrossesMitainesAPI/Controllers/ProductController.cs +++ b/GrossesMitaines/GrossesMitainesAPI/Controllers/ProductController.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; +using GrossesMitainesAPI.Services; namespace GrossesMitainesAPI.Controllers; @@ -14,10 +15,12 @@ namespace GrossesMitainesAPI.Controllers; public class ProductController : ControllerBase { private readonly ILogger _logger; private readonly InventoryContext _context; + private readonly DatabaseCacheService _cache; - public ProductController(ILogger logger, InventoryContext context) { + public ProductController(ILogger logger, InventoryContext context, DatabaseCacheService cache) { _logger = logger; _context = context; + _cache = cache; } [EnableCors("_myAllowSpecificOrigins")] @@ -44,6 +47,7 @@ public class ProductController : ControllerBase { try { _context.Products.Add(prod); _context.SaveChanges(); + _cache.askForRefresh(); } catch (Exception e) { _logger.LogError(8, e.Message); @@ -59,6 +63,7 @@ public class ProductController : ControllerBase { try { _context.Products.Update(prod); _context.SaveChanges(); + _cache.askForRefresh(); } catch (Exception e) { _logger.LogError(8, e.Message); @@ -74,6 +79,7 @@ public class ProductController : ControllerBase { try { _context.Products.Remove(_context.Products.Where(x => x.Id == id).First()); _context.SaveChanges(); + _cache.askForRefresh(); } catch (Exception e) { _logger.LogError(8, e.Message); diff --git a/GrossesMitaines/GrossesMitainesAPI/Controllers/SearchController.cs b/GrossesMitaines/GrossesMitainesAPI/Controllers/SearchController.cs index 35f23dc..0ff9a16 100644 --- a/GrossesMitaines/GrossesMitainesAPI/Controllers/SearchController.cs +++ b/GrossesMitaines/GrossesMitainesAPI/Controllers/SearchController.cs @@ -5,6 +5,7 @@ using GrossesMitainesAPI.Data; using Microsoft.Extensions.Logging; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Cors; +using GrossesMitainesAPI.Services; namespace GrossesMitainesAPI.Controllers; @@ -13,22 +14,74 @@ namespace GrossesMitainesAPI.Controllers; public class SearchController : Controller { private readonly ILogger _logger; private readonly InventoryContext _context; - private static List _searchCache = new(); - private static DateTime? _cacheDate; + private readonly DatabaseCacheService _cache; + private Product[]? _searchCache = null; + private const int PREVIEW = 4; - public SearchController(ILogger logger, InventoryContext context) { + + public SearchController(ILogger logger, InventoryContext context, DatabaseCacheService cache) { _logger = logger; _context = context; - if (_searchCache.Count == 0 || _cacheDate.HasValue && _cacheDate.Value.AddMinutes(5) < DateTime.Now) { - _searchCache = _context.Products.ToList(); - _cacheDate = DateTime.Now; - } + _cache = cache; + + if (_cache.isOk()) + _searchCache = _cache.GetCacheCopy(); } [EnableCors("_myAllowSpecificOrigins")] [HttpPost(Name = "Search")] public IEnumerable Post(string query, bool? preview, bool? deep) { - const int PREVIEW = 4; + if (_searchCache is not null) + return SearchCached(query, preview, deep); + else return Search(query, preview, deep); + + } + private List Search(string query, bool? preview, bool? deep) { + List products = new(); + + query = query.Trim(); + + try { // Pour faire une liste priorisée. + if (preview.HasValue && preview == true) + products = _context.Products.Where(x => x.Title.Contains(query)).Take(PREVIEW).ToList(); + else { + if (deep.HasValue && deep == true) { + List title = new(), desc = new(), cat = new(); + query = query.ToLower(); + + foreach (Product prod in _context.Products.ToList()) { + string sTitle = prod.Title.Replace(",", " ").ToLower(), + sCat = prod.Category.ToLower(), + sDesc = prod.Description.Replace(".", " ").Replace(",", " ").ToLower(); + if (sTitle.StartsWith(query)) + products.Add(prod); + else if (sTitle.Contains(" " + query + " ")) + title.Add(prod); + else if (sDesc.StartsWith(query) || sDesc.Contains(" " + query + " ")) + desc.Add(prod); + else if (sCat.Contains(query)) + cat.Add(prod); + } + products.AddRange(title); + products.AddRange(desc); + products.AddRange(cat); + } else { + products = _context.Products.Where(x => x.Title.Contains(query)).ToList(); + foreach (Product prod in _context.Products.Where(x => x.Description.Contains(query)).ToList()) + if (!products.Contains(prod)) + products.Add(prod); + foreach (Product prod in _context.Products.Where(x => x.Category.Contains(query)).ToList()) + if (!products.Contains(prod)) + products.Add(prod); + } + } + } catch (Exception e) { + _logger.LogError(8, e.Message); + } + return products; + } + + private List SearchCached(string query, bool? preview, bool? deep) { List products = new(); query = query.Trim(); diff --git a/GrossesMitaines/GrossesMitainesAPI/Program.cs b/GrossesMitaines/GrossesMitainesAPI/Program.cs index e1921cd..d697aed 100644 --- a/GrossesMitaines/GrossesMitainesAPI/Program.cs +++ b/GrossesMitaines/GrossesMitainesAPI/Program.cs @@ -1,4 +1,5 @@ using GrossesMitainesAPI.Data; +using GrossesMitainesAPI.Services; using Microsoft.EntityFrameworkCore; var MyAllowSpecificOrigins = "_myAllowSpecificOrigins"; @@ -21,6 +22,9 @@ builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddDbContextFactory(options => { options.UseSqlServer("DefaultConnection"); }); +builder.Services.AddSingleton(); +builder.Services.BuildServiceProvider().GetRequiredService(); + var app = builder.Build(); // Configure the HTTP request pipeline. diff --git a/GrossesMitaines/GrossesMitainesAPI/Services/DatabaseCacheService.cs b/GrossesMitaines/GrossesMitainesAPI/Services/DatabaseCacheService.cs new file mode 100644 index 0000000..bbe4033 --- /dev/null +++ b/GrossesMitaines/GrossesMitainesAPI/Services/DatabaseCacheService.cs @@ -0,0 +1,64 @@ +using GrossesMitainesAPI.Data; +using GrossesMitainesAPI.Models; +using Microsoft.EntityFrameworkCore; + +namespace GrossesMitainesAPI.Services { + public class DatabaseCacheService { + private readonly IServiceScopeFactory _contextFactory; // https://entityframeworkcore.com/knowledge-base/51939451/how-to-use-a-database-context-in-a-singleton-service- + private readonly ILogger _logger; + private Product[] _cache = new Product[1]; + private bool _ok = false, _needUpd = true; + private PeriodicTimer _timer = new PeriodicTimer(TimeSpan.FromSeconds(10)); + + public DatabaseCacheService(ILogger logger, IServiceScopeFactory scopeFactory) { + _contextFactory = scopeFactory; + _logger = logger; + _ok = UpdateCache(); + _needUpd = !_ok; + UpdateJob(); + } + + private async void UpdateJob() { + while (await _timer.WaitForNextTickAsync()) + if (_needUpd) { + _ok = UpdateCache(); + _needUpd = !_ok; + } + } + + 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; + } + + public bool isOk() { return _ok; } + public void askForRefresh() { _needUpd = true; } + 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; + } + } +}