using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Collections; using System.Diagnostics; namespace AsbCloudInfrastructure.Services.Cache { public class CacheTable : IEnumerable where TEntity : class { private const int semaphoreTimeout = 5_000; private static readonly SemaphoreSlim semaphore = new(1); private readonly DbContext context; private (DateTime refreshDate, IEnumerable entities) data; private readonly List cached; private readonly DbSet dbSet; internal CacheTable(DbContext context, (DateTime refreshDate, IEnumerable entities) data) { this.context = context; this.data = data; dbSet = context.Set(); cached = (List)data.entities; if (cached.Count == 0) Refresh(); } public TEntity this[int index] { get => cached.ElementAt(index); } //public static void Sync(Action) public int Refresh() { var wasFree = semaphore.CurrentCount > 0; if(!semaphore.Wait(semaphoreTimeout)) return 0; try { if (wasFree) { cached.Clear(); var entities = dbSet.AsNoTracking().ToList(); cached.AddRange(entities); data.refreshDate = DateTime.Now; } //else - nothing, it was just updated in another thread } catch (Exception ex) { Trace.WriteLine($"{DateTime.Now:yyyy.MM.dd HH:mm:ss:fff} error in CacheTable<{typeof(TEntity).Name}>.Refresh()"); Trace.WriteLine(ex.Message); } finally { semaphore.Release(); } return cached.Count; } public async Task RefreshAsync(CancellationToken token = default) { var wasFree = semaphore.CurrentCount > 0; if (!await semaphore.WaitAsync(semaphoreTimeout, token).ConfigureAwait(false)) return 0; try { if (wasFree) { cached.Clear(); var entities = await context.Set().AsNoTracking() .ToListAsync(token).ConfigureAwait(false); cached.AddRange(entities); data.refreshDate = DateTime.Now; } //else - nothing, it was just updated in another thread } catch (Exception ex) { Trace.WriteLine($"{DateTime.Now:yyyy.MM.dd HH:mm:ss:fff} error in CacheTable<{typeof(TEntity).Name}>.Refresh()"); Trace.WriteLine(ex.Message); } finally { semaphore.Release(); } return cached.Count; } public bool Contains(Func predicate) => FirstOrDefault(predicate) != default; public async Task ContainsAsync(Func predicate, CancellationToken token = default) => await FirstOrDefaultAsync(predicate, token) != default; public TEntity FirstOrDefault() { var result = cached.FirstOrDefault(); if (result != default) return result; Refresh(); return cached.FirstOrDefault(); } public async Task FirstOrDefaultAsync(CancellationToken token = default) { var result = cached.FirstOrDefault(); if (result != default) return result; await RefreshAsync(token); return cached.FirstOrDefault(); } public TEntity FirstOrDefault(Func predicate) { var result = cached.FirstOrDefault(predicate); if (result != default) return result; Refresh(); return cached.FirstOrDefault(predicate); } public async Task FirstOrDefaultAsync(Func predicate, CancellationToken token = default) { var result = cached.FirstOrDefault(predicate); if (result != default) return result; await RefreshAsync(token); return cached.FirstOrDefault(predicate); } public IEnumerable Where(Func predicate = default) { var result = (predicate != default) ? cached.Where(predicate) : cached; if (result.Any()) return result; Refresh(); result = (predicate != default) ? cached.Where(predicate) : cached; return result; } public async Task> WhereAsync(Func predicate = default, CancellationToken token = default) { var result = (predicate != default) ? cached.Where(predicate) : cached; if (result.Any()) return result; await RefreshAsync(token); result = (predicate != default) ? cached.Where(predicate) : cached; return result; } public TEntity Upsert(TEntity entity) { Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry updated; if (dbSet.Contains(entity)) updated = dbSet.Update(entity); else updated = dbSet.Add(entity); context.SaveChanges(); Refresh(); return updated.Entity; } public async Task UpsertAsync(TEntity entity, CancellationToken token = default) { Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry updated; if (dbSet.Contains(entity)) updated = dbSet.Update(entity); else updated = dbSet.Add(entity); await context.SaveChangesAsync(token).ConfigureAwait(false); await RefreshAsync(token).ConfigureAwait(false); return updated.Entity; } public IEnumerable Upsert(IEnumerable entities) { var upsertedEntries = new List(entities.Count()); foreach (var entity in entities) { Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry updated; if (dbSet.Contains(entity)) // TODO: это очень ммедленно updated = dbSet.Update(entity); else updated = dbSet.Add(entity); upsertedEntries.Add(updated.Entity); } context.SaveChanges(); Refresh(); return upsertedEntries; } public async Task> UpsertAsync(IEnumerable entities, CancellationToken token = default) { var upsertedEntries = new List(entities.Count()); foreach (var entity in entities) { Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry updated; if (dbSet.Contains(entity)) updated = dbSet.Update(entity); else updated = dbSet.Add(entity); upsertedEntries.Add(updated.Entity); } await context.SaveChangesAsync(token).ConfigureAwait(false); await RefreshAsync(token).ConfigureAwait(false); return upsertedEntries; } public void Remove(Func predicate) { dbSet.RemoveRange(dbSet.Where(predicate)); context.SaveChanges(); Refresh(); return; } public async Task RemoveAsync(Func predicate, CancellationToken token = default) { dbSet.RemoveRange(dbSet.Where(predicate)); await context.SaveChangesAsync(token).ConfigureAwait(false); await RefreshAsync(token).ConfigureAwait(false); return; } public TEntity Insert(TEntity entity) { var entry = dbSet.Add(entity); context.SaveChanges(); Refresh(); return entry.Entity; } public async Task InsertAsync(TEntity entity, CancellationToken token = default) { var entry = dbSet.Add(entity); await context.SaveChangesAsync(token).ConfigureAwait(false); await RefreshAsync(token).ConfigureAwait(false); return entry.Entity; } public int Insert(IEnumerable newEntities) { dbSet.AddRange(newEntities); var result = context.SaveChanges(); Refresh(); return result; } public async Task InsertAsync(IEnumerable newEntities, CancellationToken token = default) { dbSet.AddRange(newEntities); var result = await context.SaveChangesAsync(token).ConfigureAwait(false); await RefreshAsync(token).ConfigureAwait(false); return result; } public IEnumerator GetEnumerator() => Where().GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } }