using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace AsbCloudInfrastructure.Services.Cache { public class CacheTable<TEntity> : IEnumerable<TEntity> where TEntity : class { private readonly DbContext context; private (DateTime refreshDate, IEnumerable<object> entities) data; private readonly List<TEntity> cached; private readonly DbSet<TEntity> dbSet; internal CacheTable(DbContext context, (DateTime refreshDate, IEnumerable<object> entities) data) { this.context = context; this.data = data; this.cached = (List<TEntity>)data.entities; dbSet = context.Set<TEntity>(); } public TEntity this[int index] { get => cached.ElementAt(index); } public int Refresh() { if (cached.Any()) { try { cached.Clear(); } catch { // ignore // TODO: figure out what the hell } } var dbEntities = context.Set<TEntity>().AsNoTracking().ToList(); cached.AddRange(dbEntities); data.refreshDate = DateTime.Now; return cached.Count; } public async Task<int> RefreshAsync(CancellationToken token = default) { if (cached.Any()) { try { cached.Clear(); } catch { // ignore // TODO: figure out what the well } } var dbEntities = await context.Set<TEntity>().AsNoTracking().ToListAsync(token).ConfigureAwait(false); cached.AddRange(dbEntities); data.refreshDate = DateTime.Now; return cached.Count; } private bool CheckRefresh(RefreshMode refreshMode) { if (refreshMode == RefreshMode.Force) { Refresh(); return true; } if ((!cached.Any()) && (refreshMode == RefreshMode.IfResultEmpty)) { Refresh(); return true; } return false; } private async Task<bool> CheckRefreshAsync(RefreshMode refreshMode, CancellationToken token = default) { if (refreshMode == RefreshMode.Force) { await RefreshAsync(token); return true; } if ((!cached.Any()) && (refreshMode == RefreshMode.IfResultEmpty)) { await RefreshAsync(token); return true; } return false; } public bool Contains(Func<TEntity, bool> predicate, RefreshMode refreshMode = RefreshMode.IfResultEmpty) => FirstOrDefault(predicate, refreshMode) != default; public Task<bool> ContainsAsync(Func<TEntity, bool> predicate, CancellationToken token = default) => ContainsAsync(predicate, RefreshMode.IfResultEmpty, token); public async Task<bool> ContainsAsync(Func<TEntity, bool> predicate, RefreshMode refreshMode = RefreshMode.IfResultEmpty, CancellationToken token = default) => await FirstOrDefaultAsync(predicate, refreshMode, token) != default; public Task<TEntity> FirstOrDefaultAsync(CancellationToken token = default) => FirstOrDefaultAsync(RefreshMode.IfResultEmpty, token); public TEntity FirstOrDefault(RefreshMode refreshMode = RefreshMode.IfResultEmpty) { bool isUpdated = CheckRefresh(refreshMode); var result = cached.FirstOrDefault(); if (result == default && refreshMode == RefreshMode.IfResultEmpty && !isUpdated) { Refresh(); return cached.FirstOrDefault(); } return result; } public async Task<TEntity> FirstOrDefaultAsync(RefreshMode refreshMode = RefreshMode.IfResultEmpty, CancellationToken token = default) { bool isUpdated = await CheckRefreshAsync(refreshMode, token); var result = cached.FirstOrDefault(); if (result == default && refreshMode == RefreshMode.IfResultEmpty && !isUpdated) { await RefreshAsync(token); return cached.FirstOrDefault(); } return result; } public Task<TEntity> FirstOrDefaultAsync(Func<TEntity, bool> predicate, CancellationToken token = default) => FirstOrDefaultAsync(predicate, RefreshMode.IfResultEmpty, token); public TEntity FirstOrDefault(Func<TEntity, bool> predicate, RefreshMode refreshMode = RefreshMode.IfResultEmpty) { bool isUpdated = CheckRefresh(refreshMode); var result = cached.FirstOrDefault(predicate); if (result == default && refreshMode == RefreshMode.IfResultEmpty && !isUpdated) { Refresh(); return cached.FirstOrDefault(predicate); } return result; } public async Task<TEntity> FirstOrDefaultAsync(Func<TEntity, bool> predicate, RefreshMode refreshMode = RefreshMode.IfResultEmpty, CancellationToken token = default) { bool isUpdated = await CheckRefreshAsync(refreshMode, token); var result = cached.FirstOrDefault(predicate); if (result == default && refreshMode == RefreshMode.IfResultEmpty && !isUpdated) { await RefreshAsync(token); return cached.FirstOrDefault(predicate); } return result; } public Task<IEnumerable<TEntity>> WhereAsync(CancellationToken token = default) => WhereAsync(default, RefreshMode.IfResultEmpty, token); public Task<IEnumerable<TEntity>> WhereAsync(Func<TEntity, bool> predicate, CancellationToken token = default) => WhereAsync(predicate, RefreshMode.IfResultEmpty, token); public IEnumerable<TEntity> Where(Func<TEntity, bool> predicate = default, RefreshMode refreshMode = RefreshMode.IfResultEmpty) { bool isUpdated = CheckRefresh(refreshMode); var result = (predicate != default) ? cached.Where(predicate) : cached; if (!result.Any() && refreshMode == RefreshMode.IfResultEmpty && !isUpdated) { Refresh(); result = (predicate != default) ? cached.Where(predicate) : cached; } return result; } public async Task<IEnumerable<TEntity>> WhereAsync(Func<TEntity, bool> predicate = default, RefreshMode refreshMode = RefreshMode.IfResultEmpty, CancellationToken token = default) { bool isUpdated = await CheckRefreshAsync(refreshMode, token); var result = (predicate != default) ? cached.Where(predicate) : cached; if (!result.Any() && refreshMode == RefreshMode.IfResultEmpty && !isUpdated) { await RefreshAsync(token); result = (predicate != default) ? cached.Where(predicate) : cached; } return result; } public IEnumerable<TEntity> Mutate(Func<TEntity, bool> predicate, Action<TEntity> mutation) { var dbEntities = dbSet.Where(predicate); if (dbEntities.Any()) { foreach (var dbEntity in dbEntities) mutation(dbEntity); context.SaveChanges(); } cached.RemoveAll(e => predicate(e)); cached.AddRange(dbEntities); return dbEntities; } public async Task<IEnumerable<TEntity>> MutateAsync(Func<TEntity, bool> predicate, Action<TEntity> mutation, CancellationToken token = default) { var dbEntities = dbSet.Where(predicate); if (dbEntities.Any()) { foreach (var dbEntity in dbEntities) mutation(dbEntity); await context.SaveChangesAsync(token).ConfigureAwait(false); } cached.RemoveAll(e => predicate(e)); cached.AddRange(dbEntities); return dbEntities; } public TEntity Upsert(TEntity entity) { var updated = dbSet.Update(entity); context.SaveChanges(); Refresh(); return updated.Entity; } public async Task<TEntity> UpsertAsync(TEntity entity, CancellationToken token = default) { Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry<TEntity> 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<TEntity> Upsert(IEnumerable<TEntity> entities) { var upsertedEntries = new List<TEntity>(entities.Count()); foreach (var entity in entities) { Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry<TEntity> 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<IEnumerable<TEntity>> UpsertAsync(IEnumerable<TEntity> entities, CancellationToken token = default) { var upsertedEntries = new List<TEntity>(entities.Count()); foreach (var entity in entities) { Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry<TEntity> 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<TEntity, bool> predicate) { cached.RemoveAll(e => predicate(e)); dbSet.RemoveRange(dbSet.Where(predicate)); context.SaveChanges(); return; } public async Task RemoveAsync(Func<TEntity, bool> predicate, CancellationToken token = default) { cached.RemoveAll(e => predicate(e)); dbSet.RemoveRange(dbSet.Where(predicate)); await context.SaveChangesAsync(token).ConfigureAwait(false); return; } public TEntity Insert(TEntity entity) { var dbEntity = dbSet.Add(entity).Entity; context.SaveChanges(); cached.Add(dbEntity); return dbEntity; } public async Task<TEntity> InsertAsync(TEntity entity, CancellationToken token = default) { var dbEntity = dbSet.Add(entity).Entity; await context.SaveChangesAsync(token).ConfigureAwait(false); cached.Add(dbEntity); return dbEntity; } public IEnumerable<TEntity> Insert(IEnumerable<TEntity> newEntities) { var dbEntities = new List<TEntity>(newEntities.Count()); foreach (var item in newEntities) dbEntities.Add(dbSet.Add(item).Entity); context.SaveChanges(); cached.AddRange(dbEntities); return dbEntities; } public async Task<IEnumerable<TEntity>> InsertAsync(IEnumerable<TEntity> newEntities, CancellationToken token = default) { var dbEntities = new List<TEntity>(newEntities.Count()); foreach (var item in newEntities) dbEntities.Add(dbSet.Add(item).Entity); await context.SaveChangesAsync(token).ConfigureAwait(false); cached.AddRange(dbEntities); return dbEntities; } public IEnumerator<TEntity> GetEnumerator() => Where().GetEnumerator(); System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); } }