DD.WellWorkover.Cloud/AsbCloudInfrastructure/Services/Cache/CacheTable.cs

409 lines
14 KiB
C#
Raw Normal View History

2021-04-07 18:01:56 +05:00
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
2021-10-03 20:08:17 +05:00
using System.Collections;
using System.Diagnostics;
2021-04-07 18:01:56 +05:00
namespace AsbCloudInfrastructure.Services.Cache
{
2021-09-08 11:51:55 +05:00
public class CacheTable<TEntity> : IEnumerable<TEntity>
where TEntity : class
2021-04-07 18:01:56 +05:00
{
2021-10-03 20:08:17 +05:00
private const int semaphoreTimeout = 5_000;
private static readonly SemaphoreSlim semaphore = new(1);
private static readonly TimeSpan minPeriodRefresh = TimeSpan.FromSeconds(5);
2021-11-10 17:52:28 +05:00
private static readonly string nameOfTEntity = typeof(TEntity).Name;
2021-11-10 17:01:18 +05:00
private readonly CacheTableDataStore data;
private readonly Func<DbSet<TEntity>, IQueryable<TEntity>> configureDbSet;
2021-10-03 20:08:17 +05:00
private readonly List<TEntity> cached;
private readonly DbContext context;
private readonly DbSet<TEntity> dbSet;
2021-04-07 18:01:56 +05:00
internal CacheTable(DbContext context, CacheTableDataStore data, IEnumerable<string> includes = null)
2021-04-07 18:01:56 +05:00
{
this.context = context;
this.data = data;
dbSet = context.Set<TEntity>();
if (includes?.Any() == true)
configureDbSet = (DbSet<TEntity> dbSet) =>
{
IQueryable<TEntity> result = dbSet;
foreach (var include in includes)
result = result.Include(include);
return result;
};
cached = (List<TEntity>)data.Entities;
if ((cached.Count == 0) || data.IsObsolete)
Refresh(false);
}
internal CacheTable(DbContext context, CacheTableDataStore data, Func<DbSet<TEntity>, IQueryable<TEntity>> configureDbSet = null)
{
this.context = context;
this.data = data;
this.configureDbSet = configureDbSet;
dbSet = context.Set<TEntity>();
cached = (List<TEntity>)data.Entities;
if ((cached.Count == 0) || data.IsObsolete)
2021-11-10 17:01:18 +05:00
Refresh(false);
2021-04-07 18:01:56 +05:00
}
2021-04-23 10:21:25 +05:00
public TEntity this[int index] { get => cached.ElementAt(index); }
2021-04-07 18:01:56 +05:00
2021-10-04 15:52:22 +05:00
/// <summary>
/// Runs action like atomic operation.
/// wasFree is action argument indicates that semaphore was free.
/// It may be needed to avoid multiple operations like Refresh().
/// </summary>
/// <param name="action">(wasFree) => {...}</param>
/// <returns>default if semaphoreTimeout. Or result of func(..)</returns>
private static T Sync<T>(Func<bool, T> func)
2021-04-07 18:01:56 +05:00
{
2021-10-03 20:08:17 +05:00
var wasFree = semaphore.CurrentCount > 0;
T result = default;
if (func is null || !semaphore.Wait(semaphoreTimeout))
return result;
2021-10-03 20:08:17 +05:00
try
2021-04-07 18:01:56 +05:00
{
result = func.Invoke(wasFree);
2021-04-07 18:01:56 +05:00
}
2021-10-03 20:08:17 +05:00
catch (Exception ex)
2021-04-07 18:01:56 +05:00
{
Trace.WriteLine($"{DateTime.Now:yyyy.MM.dd HH:mm:ss:fff} error in CacheTable<{nameOfTEntity}>.Sync()");
2021-10-03 20:08:17 +05:00
Trace.WriteLine(ex.Message);
2021-10-04 15:52:22 +05:00
Trace.WriteLine(ex.StackTrace);
2021-04-07 18:01:56 +05:00
}
2021-10-03 20:08:17 +05:00
finally
{
semaphore.Release();
}
return result;
2021-04-07 18:01:56 +05:00
}
2021-10-04 15:52:22 +05:00
/// <summary>
/// Runs action like atomic operation.
/// wasFree is action argument indicates that semaphore was free.
/// It may be needed to avoid multiple operations like Refresh().
/// </summary>
/// <param name="action">(wasFree) => {...}</param>
/// <returns>default if semaphoreTimeout. Or result of func(..)</returns>
private static async Task<T> SyncAsync<T>(Func<bool, CancellationToken, Task<T>> funcAsync, CancellationToken token = default)
2021-04-07 18:01:56 +05:00
{
2021-10-03 20:08:17 +05:00
var wasFree = semaphore.CurrentCount > 0;
T result = default;
if (funcAsync is null || !await semaphore.WaitAsync(semaphoreTimeout, token).ConfigureAwait(false))
return result;
2021-10-03 20:08:17 +05:00
try
2021-04-07 18:01:56 +05:00
{
result = await funcAsync.Invoke(wasFree, token);
2021-04-07 18:01:56 +05:00
}
2021-10-03 20:08:17 +05:00
catch (Exception ex)
2021-04-07 18:01:56 +05:00
{
Trace.WriteLine($"{DateTime.Now:yyyy.MM.dd HH:mm:ss:fff} error in CacheTable<{nameOfTEntity}>.SyncAsync()");
2021-10-03 20:08:17 +05:00
Trace.WriteLine(ex.Message);
2021-10-04 15:52:22 +05:00
Trace.WriteLine(ex.StackTrace);
2021-04-07 18:01:56 +05:00
}
2021-10-03 20:08:17 +05:00
finally
{
semaphore.Release();
}
return result;
2021-10-04 15:52:22 +05:00
}
private int InternalRefresh(bool force)
2021-10-04 15:52:22 +05:00
{
if (force || data.LastResreshDate + minPeriodRefresh < DateTime.Now)
2021-11-10 17:52:28 +05:00
{
cached.Clear();
IQueryable<TEntity> query = configureDbSet is null ? dbSet : configureDbSet(dbSet);
var entities = query.AsNoTracking().ToList();
2021-11-11 15:59:29 +05:00
//Trace.WriteLine($"CacheTable<{nameOfTEntity}> refresh");
2021-11-10 17:52:28 +05:00
cached.AddRange(entities);
data.LastResreshDate = DateTime.Now;
2021-11-10 17:52:28 +05:00
}
return cached.Count;
2021-10-04 15:52:22 +05:00
}
private async Task<int> InternalRefreshAsync(bool force, CancellationToken token = default)
2021-10-04 15:52:22 +05:00
{
if (force || data.LastResreshDate + minPeriodRefresh < DateTime.Now)
2021-11-10 17:52:28 +05:00
{
cached.Clear();
IQueryable<TEntity> query = configureDbSet is null ? dbSet : configureDbSet(dbSet);
var entities = await query.AsNoTracking()
2021-11-10 17:52:28 +05:00
.ToListAsync(token).ConfigureAwait(false);
2021-11-11 15:59:29 +05:00
//Trace.WriteLine($"CacheTable<{nameOfTEntity}> refreshAsync");
2021-11-10 17:52:28 +05:00
cached.AddRange(entities);
data.LastResreshDate = DateTime.Now;
2021-11-10 17:52:28 +05:00
}
return cached.Count;
2021-10-04 15:52:22 +05:00
}
2021-11-10 17:01:18 +05:00
public int Refresh(bool force)
=> Sync((wasFree) => wasFree ? InternalRefresh(force) : 0);
2021-10-04 15:52:22 +05:00
public Task<int> RefreshAsync(bool force, CancellationToken token = default)
2021-10-04 15:52:22 +05:00
{
return SyncAsync(async (wasFree, token) =>
{
return wasFree ? await InternalRefreshAsync(force, token).ConfigureAwait(false) : 0;
}, token);
2021-04-07 18:01:56 +05:00
}
2021-10-03 20:08:17 +05:00
public bool Contains(Func<TEntity, bool> predicate)
=> FirstOrDefault(predicate) != default;
2021-04-07 18:01:56 +05:00
2021-10-03 20:08:17 +05:00
public async Task<bool> ContainsAsync(Func<TEntity, bool> predicate, CancellationToken token = default)
=> await FirstOrDefaultAsync(predicate, token) != default;
2021-04-07 18:01:56 +05:00
2021-10-04 15:52:22 +05:00
public TEntity GetOrCreate(Func<TEntity, bool> predicate, Func<TEntity> makeNew)
=> Sync(wasFree =>
2021-10-04 15:52:22 +05:00
{
var result = cached.FirstOrDefault(predicate);
if (result != default)
return result;
InternalRefresh(true);
result = cached.FirstOrDefault(predicate);
if (result != default)
return result;
var entry = dbSet.Add(makeNew());
context.SaveChanges();
InternalRefresh(true);
return entry.Entity;
});
2021-10-04 15:52:22 +05:00
2021-10-03 20:08:17 +05:00
public TEntity FirstOrDefault()
2021-04-07 18:01:56 +05:00
{
2021-04-23 10:21:25 +05:00
var result = cached.FirstOrDefault();
2021-10-03 20:08:17 +05:00
if (result != default)
return result;
2021-11-10 17:01:18 +05:00
Refresh(false);
2021-10-03 20:08:17 +05:00
return cached.FirstOrDefault();
2021-04-07 18:01:56 +05:00
}
2021-10-03 20:08:17 +05:00
public async Task<TEntity> FirstOrDefaultAsync(CancellationToken token = default)
2021-04-07 18:01:56 +05:00
{
2021-04-23 10:21:25 +05:00
var result = cached.FirstOrDefault();
2021-10-03 20:08:17 +05:00
if (result != default)
return result;
2021-04-07 18:01:56 +05:00
2021-11-10 17:01:18 +05:00
await RefreshAsync(false, token);
return cached.FirstOrDefault();
2021-10-03 20:08:17 +05:00
}
2021-04-07 18:01:56 +05:00
2021-10-03 20:08:17 +05:00
public TEntity FirstOrDefault(Func<TEntity, bool> predicate)
2021-04-07 18:01:56 +05:00
{
2021-04-23 10:21:25 +05:00
var result = cached.FirstOrDefault(predicate);
2021-10-03 20:08:17 +05:00
if (result != default)
return result;
2021-11-10 17:01:18 +05:00
Refresh(false);
2021-10-03 20:08:17 +05:00
return cached.FirstOrDefault(predicate);
2021-04-07 18:01:56 +05:00
}
2021-10-03 20:08:17 +05:00
public async Task<TEntity> FirstOrDefaultAsync(Func<TEntity, bool> predicate, CancellationToken token = default)
2021-04-07 18:01:56 +05:00
{
2021-04-23 10:21:25 +05:00
var result = cached.FirstOrDefault(predicate);
2021-10-03 20:08:17 +05:00
if (result != default)
return result;
2021-08-27 17:55:22 +05:00
2021-11-10 17:01:18 +05:00
await RefreshAsync(false, token);
2021-10-03 20:08:17 +05:00
return cached.FirstOrDefault(predicate);
}
2021-04-07 18:01:56 +05:00
2021-10-03 20:08:17 +05:00
public IEnumerable<TEntity> Where(Func<TEntity, bool> predicate = default)
2021-04-07 18:01:56 +05:00
{
var result = (predicate != default)
? cached.Where(predicate)
: cached;
2021-10-03 20:08:17 +05:00
if (result.Any())
return result;
2021-11-10 17:01:18 +05:00
Refresh(false);
2021-10-03 20:08:17 +05:00
result = (predicate != default)
? cached.Where(predicate)
: cached;
2021-04-07 18:01:56 +05:00
return result;
}
2021-10-04 15:52:22 +05:00
public Task<IEnumerable<TEntity>> WhereAsync(CancellationToken token = default) =>
WhereAsync(default, token);
public async Task<IEnumerable<TEntity>> WhereAsync(Func<TEntity, bool> predicate = default,
2021-10-03 20:08:17 +05:00
CancellationToken token = default)
2021-04-07 18:01:56 +05:00
{
var result = (predicate != default)
? cached.Where(predicate)
: cached;
2021-10-03 20:08:17 +05:00
if (result.Any())
return result;
2021-11-10 17:01:18 +05:00
await RefreshAsync(false, token);
2021-10-03 20:08:17 +05:00
result = (predicate != default)
? cached.Where(predicate)
: cached;
2021-04-07 18:01:56 +05:00
return result;
}
public int Upsert(TEntity entity)
2021-04-23 10:21:25 +05:00
{
2021-10-04 15:52:22 +05:00
if (entity == default)
return 0;
return Sync((wasFree) =>
2021-07-20 12:28:56 +05:00
{
2021-10-04 15:52:22 +05:00
if (dbSet.Contains(entity))
dbSet.Update(entity);
2021-07-20 12:28:56 +05:00
else
2021-10-04 15:52:22 +05:00
dbSet.Add(entity);
var affected = context.SaveChanges();
if (affected > 0)
InternalRefresh(true);
return affected;
});
2021-04-23 10:21:25 +05:00
}
public Task<int> UpsertAsync(TEntity entity, CancellationToken token = default)
=> SyncAsync(async (wasFree, token) =>
{
if (dbSet.Contains(entity))
dbSet.Update(entity);
else
dbSet.Add(entity);
var affected = await context.SaveChangesAsync(token).ConfigureAwait(false);
if (affected > 0)
await InternalRefreshAsync(true, token).ConfigureAwait(false);
return affected;
}, token);
public int Upsert(IEnumerable<TEntity> entities)
2021-04-07 18:01:56 +05:00
{
2021-10-04 15:52:22 +05:00
if (!entities.Any())
return 0;
2021-10-04 15:52:22 +05:00
return Sync((wasFree) =>
2021-10-04 15:52:22 +05:00
{
foreach (var entity in entities)
{
if (dbSet.Contains(entity)) // TODO: это очень ммедленно
dbSet.Update(entity);
else
dbSet.Add(entity);
2021-10-04 15:52:22 +05:00
}
var affected = context.SaveChanges();
if (affected > 0)
InternalRefresh(true);
return affected;
2021-10-04 15:52:22 +05:00
});
2021-04-07 18:01:56 +05:00
}
public Task<int> UpsertAsync(IEnumerable<TEntity> entities, CancellationToken token = default)
2021-04-07 18:01:56 +05:00
{
2021-10-04 15:52:22 +05:00
if (!entities.Any())
return Task.FromResult(0);
2021-10-04 15:52:22 +05:00
return SyncAsync(async (wasFree, token) =>
{
2021-10-04 15:52:22 +05:00
var upsertedEntries = new List<TEntity>(entities.Count());
foreach (var entity in entities)
{
if (dbSet.Contains(entity))
dbSet.Update(entity);
else
dbSet.Add(entity);
}
var affected = await context.SaveChangesAsync(token).ConfigureAwait(false);
if (affected > 0)
await InternalRefreshAsync(true, token).ConfigureAwait(false);
return affected;
2021-10-04 15:52:22 +05:00
}, token);
2021-04-07 18:01:56 +05:00
}
public int Remove(Func<TEntity, bool> predicate)
2021-10-04 15:52:22 +05:00
=> Sync(_ =>
{
dbSet.RemoveRange(dbSet.Where(predicate));
var affected = context.SaveChanges();
if (affected > 0)
InternalRefresh(true);
return affected;
2021-10-04 15:52:22 +05:00
});
public Task<int> RemoveAsync(Func<TEntity, bool> predicate, CancellationToken token = default)
=> SyncAsync(async (wasFree, token) =>
{
2021-10-04 15:52:22 +05:00
dbSet.RemoveRange(dbSet.Where(predicate));
var affected = await context.SaveChangesAsync(token).ConfigureAwait(false);
if (affected > 0)
await InternalRefreshAsync(true, token).ConfigureAwait(false);
return affected;
2021-10-04 15:52:22 +05:00
}, token);
2021-04-07 18:01:56 +05:00
public TEntity Insert(TEntity entity)
{
return Sync(_ =>
2021-10-04 15:52:22 +05:00
{
var entry = dbSet.Add(entity);
var affected = context.SaveChanges();
if (affected > 0)
InternalRefresh(true);
return entry.Entity;
2021-10-04 15:52:22 +05:00
});
2021-04-07 18:01:56 +05:00
}
public Task<TEntity> InsertAsync(TEntity entity, CancellationToken token = default)
2021-04-07 18:01:56 +05:00
{
return SyncAsync(async (wasFree, token) =>
{
var entry = dbSet.Add(entity);
var affected = await context.SaveChangesAsync(token).ConfigureAwait(false);
if (affected > 0)
await InternalRefreshAsync(true, token).ConfigureAwait(false);
return entry.Entity;
}, token);
2021-04-07 18:01:56 +05:00
}
2021-10-03 20:08:17 +05:00
public int Insert(IEnumerable<TEntity> newEntities)
2021-04-07 18:01:56 +05:00
{
return Sync(_ =>
{
dbSet.AddRange(newEntities);
var affected = context.SaveChanges();
if (affected > 0)
InternalRefresh(true);
return affected;
});
2021-04-07 18:01:56 +05:00
}
public Task<int> InsertAsync(IEnumerable<TEntity> newEntities, CancellationToken token = default)
2021-04-07 18:01:56 +05:00
{
return SyncAsync(async (wasFree, token) =>
{
2021-10-04 15:52:22 +05:00
dbSet.AddRange(newEntities);
var affected = await context.SaveChangesAsync(token).ConfigureAwait(false);
if (affected > 0)
await InternalRefreshAsync(true, token).ConfigureAwait(false);
return affected;
2021-10-04 15:52:22 +05:00
}, token);
}
2021-09-08 11:51:55 +05:00
public IEnumerator<TEntity> GetEnumerator() => Where().GetEnumerator();
2021-10-03 20:08:17 +05:00
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
2021-04-07 18:01:56 +05:00
}
}