forked from ddrilling/AsbCloudServer
remove CacheDB from DI and project
This commit is contained in:
parent
e837baf5e7
commit
b2844cd5b2
@ -8,7 +8,6 @@ using AsbCloudDb.Model;
|
||||
using AsbCloudDb.Model.Subsystems;
|
||||
using AsbCloudInfrastructure.Repository;
|
||||
using AsbCloudInfrastructure.Services;
|
||||
using AsbCloudInfrastructure.Services.Cache;
|
||||
using AsbCloudInfrastructure.Services.DailyReport;
|
||||
using AsbCloudInfrastructure.Services.DetectOperations;
|
||||
using AsbCloudInfrastructure.Services.DrillingProgram;
|
||||
@ -101,7 +100,6 @@ namespace AsbCloudInfrastructure
|
||||
services.AddHostedService<OperationDetectionBackgroundService>();
|
||||
services.AddHostedService<SubsystemOperationTimeBackgroundService>();
|
||||
services.AddSingleton(new WitsInfoService());
|
||||
services.AddSingleton(new CacheDb());
|
||||
services.AddSingleton(new InstantDataRepository());
|
||||
services.AddSingleton(provider=> TelemetryDataCache<TelemetryDataSaubDto>.GetInstance<TelemetryDataSaub>(configuration));
|
||||
services.AddSingleton(provider=> TelemetryDataCache<TelemetryDataSpinDto>.GetInstance<TelemetryDataSpin>(configuration));
|
||||
|
@ -1,50 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace AsbCloudInfrastructure.Services.Cache
|
||||
{
|
||||
public class CacheDb
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, CacheTableDataStore> cache =
|
||||
new ConcurrentDictionary<string, CacheTableDataStore>();
|
||||
|
||||
public CacheTable<TEntity> GetCachedTable<TEntity>(DbContext context, params string[] includes)
|
||||
where TEntity : class
|
||||
=> GetCachedTable<TEntity>(context, new SortedSet<string>(includes));
|
||||
|
||||
public CacheTable<TEntity> GetCachedTable<TEntity>(DbContext context, ISet<string> includes = null)
|
||||
where TEntity : class
|
||||
{
|
||||
var cacheItem = GetCacheTableDataStore<TEntity>();
|
||||
var tableCache = new CacheTable<TEntity>(context, cacheItem, includes);
|
||||
return tableCache;
|
||||
}
|
||||
|
||||
public CacheTable<TEntity> GetCachedTable<TEntity>(DbContext context, Func<DbSet<TEntity>, IQueryable<TEntity>> configureDbSet)
|
||||
where TEntity : class
|
||||
{
|
||||
var cacheItem = GetCacheTableDataStore<TEntity>();
|
||||
var tableCache = new CacheTable<TEntity>(context, cacheItem, configureDbSet);
|
||||
return tableCache;
|
||||
}
|
||||
|
||||
private CacheTableDataStore GetCacheTableDataStore<TEntity>()
|
||||
where TEntity : class
|
||||
{
|
||||
var nameOfTEntity = typeof(TEntity).FullName;
|
||||
var cacheItem = cache.GetOrAdd(nameOfTEntity, (nameOfTEntity) => new CacheTableDataStore
|
||||
{
|
||||
NameOfTEntity = nameOfTEntity,
|
||||
Entities = new List<TEntity>(),
|
||||
});
|
||||
return cacheItem;
|
||||
}
|
||||
|
||||
public void DropAll() => cache.Clear();
|
||||
|
||||
public void Drop<TEntity>() => cache.Remove(typeof(TEntity).FullName, out _);
|
||||
}
|
||||
}
|
@ -1,449 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
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 static readonly TimeSpan minPeriodRefresh = TimeSpan.FromSeconds(5);
|
||||
private static readonly string nameOfTEntity = typeof(TEntity).Name;
|
||||
|
||||
private readonly CacheTableDataStore data;
|
||||
private readonly Func<DbSet<TEntity>, IQueryable<TEntity>> configureDbSet;
|
||||
private readonly List<TEntity> cached;
|
||||
private readonly DbContext context;
|
||||
private readonly DbSet<TEntity> dbSet;
|
||||
|
||||
internal CacheTable(DbContext context, CacheTableDataStore data, ISet<string> includes = null)
|
||||
{
|
||||
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)
|
||||
Refresh(false);
|
||||
}
|
||||
|
||||
public TEntity this[int index]
|
||||
{
|
||||
get => cached.ElementAt(index);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
var wasFree = semaphore.CurrentCount > 0;
|
||||
T result = default;
|
||||
if (func is null || !semaphore.Wait(semaphoreTimeout))
|
||||
return result;
|
||||
|
||||
try
|
||||
{
|
||||
result = func.Invoke(wasFree);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.WriteLine($"{DateTime.Now:yyyy.MM.dd HH:mm:ss:fff} error in CacheTable<{nameOfTEntity}>.Sync()");
|
||||
Trace.WriteLine(ex.Message);
|
||||
Trace.WriteLine(ex.StackTrace);
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
var wasFree = semaphore.CurrentCount > 0;
|
||||
T result = default;
|
||||
|
||||
if (funcAsync is null || !await semaphore.WaitAsync(semaphoreTimeout, token).ConfigureAwait(false))
|
||||
return result;
|
||||
|
||||
try
|
||||
{
|
||||
result = await funcAsync.Invoke(wasFree, token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.WriteLine(
|
||||
$"{DateTime.Now:yyyy.MM.dd HH:mm:ss:fff} error in CacheTable<{nameOfTEntity}>.SyncAsync()");
|
||||
Trace.WriteLine(ex.Message);
|
||||
Trace.WriteLine(ex.StackTrace);
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private int InternalRefresh(bool force)
|
||||
{
|
||||
if (force || data.LastResreshDate + minPeriodRefresh < DateTime.Now)
|
||||
{
|
||||
cached.Clear();
|
||||
IQueryable<TEntity> query = configureDbSet is null ? dbSet : configureDbSet(dbSet);
|
||||
var entities = query.AsNoTracking().ToList();
|
||||
//Trace.WriteLine($"CacheTable<{nameOfTEntity}> refresh");
|
||||
cached.AddRange(entities);
|
||||
data.LastResreshDate = DateTime.Now;
|
||||
}
|
||||
|
||||
return cached.Count;
|
||||
}
|
||||
|
||||
private async Task<int> InternalRefreshAsync(bool force, CancellationToken token = default)
|
||||
{
|
||||
if (force || data.LastResreshDate + minPeriodRefresh < DateTime.Now)
|
||||
{
|
||||
cached.Clear();
|
||||
IQueryable<TEntity> query = configureDbSet is null ? dbSet : configureDbSet(dbSet);
|
||||
var entities = await query.AsNoTracking()
|
||||
.ToListAsync(token).ConfigureAwait(false);
|
||||
//Trace.WriteLine($"CacheTable<{nameOfTEntity}> refreshAsync");
|
||||
cached.AddRange(entities);
|
||||
data.LastResreshDate = DateTime.Now;
|
||||
}
|
||||
|
||||
return cached.Count;
|
||||
}
|
||||
|
||||
public int Refresh(bool force)
|
||||
=> Sync((wasFree) => wasFree ? InternalRefresh(force) : 0);
|
||||
|
||||
public Task<int> RefreshAsync(bool force, CancellationToken token = default)
|
||||
{
|
||||
return SyncAsync(
|
||||
async (wasFree, token) =>
|
||||
{
|
||||
return wasFree ? await InternalRefreshAsync(force, token) : 0;
|
||||
}, token);
|
||||
}
|
||||
|
||||
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 GetOrCreate(Func<TEntity, bool> predicate, Func<TEntity> makeNew)
|
||||
=> Sync(wasFree =>
|
||||
{
|
||||
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;
|
||||
});
|
||||
|
||||
public TEntity FirstOrDefault()
|
||||
{
|
||||
var result = cached.FirstOrDefault();
|
||||
if (result != default)
|
||||
return result;
|
||||
|
||||
Refresh(false);
|
||||
return cached.FirstOrDefault();
|
||||
}
|
||||
|
||||
public async Task<TEntity> FirstOrDefaultAsync(CancellationToken token = default)
|
||||
{
|
||||
var result = cached.FirstOrDefault();
|
||||
if (result != default)
|
||||
return result;
|
||||
|
||||
await RefreshAsync(false, token);
|
||||
return cached.FirstOrDefault();
|
||||
}
|
||||
|
||||
public TEntity FirstOrDefault(Func<TEntity, bool> predicate)
|
||||
{
|
||||
var result = cached.FirstOrDefault(predicate);
|
||||
if (result != default)
|
||||
return result;
|
||||
|
||||
Refresh(false);
|
||||
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(false, 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(false);
|
||||
result = (predicate != default)
|
||||
? cached.Where(predicate)
|
||||
: cached;
|
||||
return result;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<TEntity>> WhereAsync(CancellationToken token = default) =>
|
||||
WhereAsync(default, token);
|
||||
|
||||
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(false, token);
|
||||
result = (predicate != default)
|
||||
? cached.Where(predicate)
|
||||
: cached;
|
||||
return result;
|
||||
}
|
||||
|
||||
public int Upsert(TEntity entity)
|
||||
{
|
||||
if (entity == default)
|
||||
return 0;
|
||||
return Sync((wasFree) =>
|
||||
{
|
||||
if (dbSet.Contains(entity))
|
||||
dbSet.Update(entity);
|
||||
else
|
||||
dbSet.Add(entity);
|
||||
var affected = context.SaveChanges();
|
||||
if (affected > 0)
|
||||
InternalRefresh(true);
|
||||
return affected;
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
return affected;
|
||||
}, token);
|
||||
|
||||
public int Upsert(IEnumerable<TEntity> entities)
|
||||
{
|
||||
if (!entities.Any())
|
||||
return 0;
|
||||
|
||||
return Sync((wasFree) =>
|
||||
{
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
if (dbSet.Contains(entity)) // TODO: это очень медленно
|
||||
dbSet.Update(entity);
|
||||
else
|
||||
dbSet.Add(entity);
|
||||
}
|
||||
|
||||
var affected = context.SaveChanges();
|
||||
if (affected > 0)
|
||||
InternalRefresh(true);
|
||||
return affected;
|
||||
});
|
||||
}
|
||||
|
||||
public Task<int> UpsertAsync(IEnumerable<TEntity> entities, CancellationToken token = default)
|
||||
{
|
||||
if (!entities.Any())
|
||||
return Task.FromResult(0);
|
||||
|
||||
return SyncAsync(async (wasFree, token) =>
|
||||
{
|
||||
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);
|
||||
return affected;
|
||||
}, token);
|
||||
}
|
||||
|
||||
public int Remove(Func<TEntity, bool> predicate)
|
||||
=> Sync(_ =>
|
||||
{
|
||||
dbSet.RemoveRange(dbSet.Where(predicate));
|
||||
var affected = context.SaveChanges();
|
||||
if (affected > 0)
|
||||
InternalRefresh(true);
|
||||
return affected;
|
||||
});
|
||||
|
||||
public Task<int> RemoveAsync(Func<TEntity, bool> predicate, CancellationToken token = default)
|
||||
=> SyncAsync(async (wasFree, token) =>
|
||||
{
|
||||
dbSet.RemoveRange(dbSet.Where(predicate));
|
||||
var affected = await context.SaveChangesAsync(token).ConfigureAwait(false);
|
||||
if (affected > 0)
|
||||
await InternalRefreshAsync(true, token);
|
||||
return affected;
|
||||
}, token);
|
||||
|
||||
public TEntity Insert(TEntity entity)
|
||||
{
|
||||
return Sync(_ =>
|
||||
{
|
||||
var entry = dbSet.Add(entity);
|
||||
var affected = context.SaveChanges();
|
||||
if (affected > 0)
|
||||
InternalRefresh(true);
|
||||
return entry.Entity;
|
||||
});
|
||||
}
|
||||
|
||||
public Task<TEntity> InsertAsync(TEntity entity, CancellationToken token = default)
|
||||
{
|
||||
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);
|
||||
return entry.Entity;
|
||||
}, token);
|
||||
}
|
||||
|
||||
public IEnumerable<TEntity> Insert(IEnumerable<TEntity> newEntities)
|
||||
{
|
||||
if (newEntities is null)
|
||||
return null;
|
||||
var count = newEntities.Count();
|
||||
if (count == 0)
|
||||
return null;
|
||||
|
||||
return Sync(_ =>
|
||||
{
|
||||
var entries = new List<Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry<TEntity>>(count);
|
||||
foreach (var newEntity in newEntities)
|
||||
{
|
||||
var entry = dbSet.Add(newEntity);
|
||||
entries.Add(entry);
|
||||
}
|
||||
|
||||
var affected = context.SaveChanges();
|
||||
if (affected > 0)
|
||||
InternalRefresh(true);
|
||||
else
|
||||
return null;
|
||||
|
||||
return entries.Select(e => e.Entity);
|
||||
});
|
||||
}
|
||||
|
||||
public Task<IEnumerable<TEntity>> InsertAsync(IEnumerable<TEntity> newEntities, CancellationToken token = default)
|
||||
{
|
||||
if (newEntities is null)
|
||||
return null;
|
||||
var count = newEntities.Count();
|
||||
if (count == 0)
|
||||
return null;
|
||||
|
||||
return SyncAsync(async (wasFree, token) =>
|
||||
{
|
||||
var entries = new List<Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry<TEntity>>(count);
|
||||
foreach (var newEntity in newEntities)
|
||||
{
|
||||
var entry = dbSet.Add(newEntity);
|
||||
entries.Add(entry);
|
||||
}
|
||||
var affected = await context.SaveChangesAsync(token).ConfigureAwait(false);
|
||||
if (affected > 0)
|
||||
await InternalRefreshAsync(true, token);
|
||||
else
|
||||
return null;
|
||||
|
||||
return entries.Select(e => e.Entity);
|
||||
}, token);
|
||||
}
|
||||
|
||||
public IEnumerator<TEntity> GetEnumerator() => Where().GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace AsbCloudInfrastructure.Services.Cache
|
||||
{
|
||||
class CacheTableDataStore
|
||||
{
|
||||
public string NameOfTEntity { get; set; }
|
||||
public DateTime LastResreshDate { get; set; }
|
||||
|
||||
//public ISet<string> Includes { get; set; } //TODO: this prop change should update entities
|
||||
public IEnumerable Entities { get; set; }
|
||||
public TimeSpan ObsolesenceTime { get; set; } = TimeSpan.FromMinutes(15);
|
||||
public bool IsObsolete => (DateTime.Now - LastResreshDate > ObsolesenceTime);
|
||||
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
using AsbCloudApp.Services;
|
||||
using AsbCloudDb;
|
||||
using AsbCloudDb.Model;
|
||||
using AsbCloudInfrastructure.Services.Cache;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -1,7 +1,6 @@
|
||||
using AsbCloudApp.Data;
|
||||
using AsbCloudApp.Services;
|
||||
using AsbCloudDb.Model;
|
||||
using AsbCloudInfrastructure.Services.Cache;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
@ -4,7 +4,6 @@ using AsbCloudApp.Services;
|
||||
using AsbCloudDb.Model;
|
||||
using AsbCloudInfrastructure.EfCache;
|
||||
using AsbCloudInfrastructure.Repository;
|
||||
using AsbCloudInfrastructure.Services.Cache;
|
||||
using Mapster;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
Loading…
Reference in New Issue
Block a user