DD.WellWorkover.Cloud/AsbCloudInfrastructure/Services/Cache/CacheTable.cs
Фролов cdfcb0b2f7 semaphore
2021-10-03 20:08:17 +05:00

282 lines
9.6 KiB
C#

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<TEntity> : IEnumerable<TEntity>
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<TEntity> cached;
private readonly DbSet<TEntity> dbSet;
internal CacheTable(DbContext context, (DateTime refreshDate, IEnumerable entities) data)
{
this.context = context;
this.data = data;
dbSet = context.Set<TEntity>();
cached = (List<TEntity>)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<int> 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<TEntity>().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<TEntity, bool> predicate)
=> FirstOrDefault(predicate) != default;
public async Task<bool> ContainsAsync(Func<TEntity, bool> 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<TEntity> FirstOrDefaultAsync(CancellationToken token = default)
{
var result = cached.FirstOrDefault();
if (result != default)
return result;
await RefreshAsync(token);
return cached.FirstOrDefault();
}
public TEntity FirstOrDefault(Func<TEntity, bool> predicate)
{
var result = cached.FirstOrDefault(predicate);
if (result != default)
return result;
Refresh();
return cached.FirstOrDefault(predicate);
}
public async Task<TEntity> FirstOrDefaultAsync(Func<TEntity, bool> predicate, CancellationToken token = default)
{
var result = cached.FirstOrDefault(predicate);
if (result != default)
return result;
await RefreshAsync(token);
return cached.FirstOrDefault(predicate);
}
public IEnumerable<TEntity> Where(Func<TEntity, bool> 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<IEnumerable<TEntity>> WhereAsync(Func<TEntity, bool> 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<TEntity> updated;
if (dbSet.Contains(entity))
updated = dbSet.Update(entity);
else
updated = dbSet.Add(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)
{
dbSet.RemoveRange(dbSet.Where(predicate));
context.SaveChanges();
Refresh();
return;
}
public async Task RemoveAsync(Func<TEntity, bool> 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<TEntity> 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<TEntity> newEntities)
{
dbSet.AddRange(newEntities);
var result = context.SaveChanges();
Refresh();
return result;
}
public async Task<int> InsertAsync(IEnumerable<TEntity> newEntities, CancellationToken token = default)
{
dbSet.AddRange(newEntities);
var result = await context.SaveChangesAsync(token).ConfigureAwait(false);
await RefreshAsync(token).ConfigureAwait(false);
return result;
}
public IEnumerator<TEntity> GetEnumerator() => Where().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}