remove EfCacheExtensions, replace by IMemoryCache

This commit is contained in:
ngfrolov 2023-02-21 18:01:03 +05:00
parent f62b23fc47
commit 539d04d3cf
Signed by untrusted user who does not match committer: ng.frolov
GPG Key ID: E99907A0357B29A7
9 changed files with 157 additions and 483 deletions

View File

@ -1,421 +0,0 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.EfCache
{
#nullable enable
/// <summary>
/// Кеширование запросов EF.<br/>
/// Кеш не отслеживается ChangeTracker.
/// </summary>
public static class EfCacheExtensions
{
private static readonly Dictionary<string, CacheItem> caches = new(16);
private static readonly TimeSpan semaphoreTimeout = TimeSpan.FromSeconds(25);
private static readonly SemaphoreSlim semaphore = new(1);
private static readonly TimeSpan minCacheTime = TimeSpan.FromSeconds(2);
private static readonly TimeSpan defaultObsolescence = TimeSpan.FromMinutes(4);
private class YieldConvertedData<TEntity, TModel> : IEnumerable<TModel>
{
private struct ConvertedData
{
public TEntity? Entity;
public TModel? Model;
}
ConvertedData[] data;
public Func<TEntity, TModel> convert { get; }
public YieldConvertedData(TEntity[] entities, Func<TEntity, TModel> convert)
{
data = (entities.Select(x => new ConvertedData {
Entity = x,
Model = default }))
.ToArray();
this.convert = convert;
}
class YieldConvertedDataEnumerator : IEnumerator<TModel>
{
private readonly ConvertedData[] data;
private readonly Func<TEntity, TModel> convert;
private int position = -1;
public YieldConvertedDataEnumerator(ConvertedData[] data, Func<TEntity, TModel> convert)
{
this.data = data;
this.convert = convert;
}
public TModel Current
{
get
{
if (data[position].Entity is TEntity entity)
{
var dto = convert(entity);
data[position].Entity = default;
data[position].Model = dto;
}
return data[position].Model!;
}
}
object IEnumerator.Current => Current!;
public void Dispose()
{
}
public bool MoveNext()
{
position++;
return (position < data.Length);
}
public void Reset()
{
position = -1;
}
}
public IEnumerator<TModel> GetEnumerator()
{
var result = new YieldConvertedDataEnumerator(data, convert);
return result;
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}
private class CacheItem
{
internal IEnumerable? Data;
internal DateTime DateObsolete;
internal DateTime DateObsoleteTotal;
internal readonly SemaphoreSlim semaphore = new(1);
internal IEnumerable<TEntity> GetData<TEntity>()
{
if (Data is IEnumerable<TEntity> typedData)
return typedData;
throw new TypeAccessException("Cache data has wrong type. Possible 'tag' is not unique.");
}
internal IEnumerable<TModel> GetData<TEntity, TModel>(Func<TEntity, TModel> convert, int attempt = 1)
{
if (Data is IEnumerable<TModel> typedData)
return typedData;
if (Data is IEnumerable<TEntity> typedEntityData)
{
if (semaphore.Wait(0))
{
try
{
var convertedData = new YieldConvertedData<TEntity, TModel>(typedEntityData.ToArray(), convert);
Data = convertedData;
return convertedData;
}
finally
{
semaphore.Release();
}
}
else
{
if (semaphore.Wait(semaphoreTimeout))
{
semaphore.Release();
}
else
{
semaphore.Release();
throw new TimeoutException("EfCacheL2.GetData. Can't wait too long while converting cache data");
}
}
}
if (attempt > 0)
return GetData(convert, --attempt);
throw new TypeAccessException("Cache data has wrong type. Possible 'tag' is not unique.");
}
}
private static CacheItem GetOrAddCache(string tag, Func<object[]> valueFactory, TimeSpan obsolete)
{
CacheItem cache;
while (!caches.ContainsKey(tag))
{
if (semaphore.Wait(0))
{
try
{
if (!caches.ContainsKey(tag))
{
cache = new CacheItem();
caches.Add(tag, cache);
}
}
finally
{
semaphore.Release();
}
break;
}
else
{
if (semaphore.Wait(semaphoreTimeout))
{
semaphore.Release();
}
else
{
semaphore.Release();
throw new TimeoutException("EfCacheL2.GetOrAddCache. Can't wait too long while getting cache");
}
}
}
cache = caches[tag];
if (cache.DateObsolete < DateTime.Now)
{
if (cache.semaphore.Wait(0))
{
try
{
var dateObsolete = DateTime.Now + obsolete;
var dateQueryStart = DateTime.Now;
var data = valueFactory();
var queryTime = DateTime.Now - dateQueryStart;
if (dateObsolete - DateTime.Now < minCacheTime)
dateObsolete = DateTime.Now + minCacheTime;
cache.Data = data;
cache.DateObsolete = dateObsolete;
cache.DateObsoleteTotal = dateObsolete + queryTime + minCacheTime;
}
finally
{
cache.semaphore.Release();
}
}
else if (cache.DateObsoleteTotal < DateTime.Now)
{
if (cache.semaphore.Wait(semaphoreTimeout))
{
cache.semaphore.Release();
}
else
{
cache.semaphore.Release();
throw new TimeoutException("EfCacheL2.GetOrAddCache. Can't wait too long while getting cache");
}
}
}
return cache;
}
private static async Task<CacheItem> GetOrAddCacheAsync(string tag, Func<CancellationToken, Task<object[]>> valueFactoryAsync, TimeSpan obsolete, CancellationToken token)
{
CacheItem cache;
while (!caches.ContainsKey(tag))
{
if (semaphore.Wait(0))
{
try
{
if (!caches.ContainsKey(tag))
{
cache = new CacheItem();
caches.Add(tag, cache);
}
}
finally
{
semaphore.Release();
}
break;
}
else
{
if (await semaphore.WaitAsync(semaphoreTimeout, token))
{
semaphore.Release();
}
else
{
semaphore.Release();
throw new TimeoutException("EfCacheL2.GetOrAddCache. Can't wait too long while getting cache");
}
}
}
cache = caches[tag];
if (cache.DateObsolete < DateTime.Now)
{
if (cache.semaphore.Wait(0))
{
try
{
var dateObsolete = DateTime.Now + obsolete;
var dateQueryStart = DateTime.Now;
var data = await valueFactoryAsync(token);
var queryTime = DateTime.Now - dateQueryStart;
if (dateObsolete - DateTime.Now < minCacheTime)
dateObsolete = DateTime.Now + minCacheTime;
cache.Data = data;
cache.DateObsolete = dateObsolete;
cache.DateObsoleteTotal = dateObsolete + queryTime + minCacheTime;
}
finally
{
cache.semaphore.Release();
}
}
else if (cache.DateObsoleteTotal < DateTime.Now)
{
if (await cache.semaphore.WaitAsync(semaphoreTimeout, token))
{
cache.semaphore.Release();
}
else
{
cache.semaphore.Release();
throw new TimeoutException("EfCacheL2.GetOrAddCache. Can't wait too long while getting updated cache");
}
}
}
return cache;
}
/// <summary>
/// Кешировать запрос в List&lt;<typeparamref name="TEntity"/>&gt;. Кеш tag = typeof(TEntity).Name
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <param name="query"></param>
/// <returns></returns>
public static IEnumerable<TEntity> FromCache<TEntity>(this IQueryable<TEntity> query)
where TEntity : class
{
var tag = typeof(TEntity).Name;
return FromCache(query, tag, defaultObsolescence);
}
/// <summary>
/// Кешировать запрос в List&lt;<typeparamref name="TEntity"/>&gt;.
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <param name="query"></param>
/// <param name="tag">Метка кеша</param>
/// <param name="obsolescence">Период устаревания данных</param>
/// <returns></returns>
public static IEnumerable<TEntity> FromCache<TEntity>(this IQueryable<TEntity> query, string tag, TimeSpan obsolescence)
where TEntity : class
{
object[] factory() => query.AsNoTracking().ToArray();
var cache = GetOrAddCache(tag, factory, obsolescence);
return cache.GetData<TEntity>();
}
/// <summary>
/// Кешировать запрос с последующим преобразованием из <typeparamref name="TEntity"/> в <typeparamref name="TModel"/>.<br/>
/// Преобразование выполняется после получения из БД, результат кешируется в List&lt;<typeparamref name="TEntity"/>&gt;.
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TModel"></typeparam>
/// <param name="query"></param>
/// <param name="tag">Метка кеша</param>
/// <param name="obsolescence">Период устаревания данных</param>
/// <param name="convert">Преобразование данных БД в DTO</param>
/// <returns></returns>
public static IEnumerable<TModel> FromCache<TEntity, TModel>(this IQueryable<TEntity> query, string tag, TimeSpan obsolescence, Func<TEntity, TModel> convert)
where TEntity : class
{
object[] factory() => query.AsNoTracking().ToArray();
var cache = GetOrAddCache(tag, factory, obsolescence);
return cache.GetData(convert);
}
public static Task<IEnumerable<TEntity>> FromCacheAsync<TEntity>(this IQueryable<TEntity> query, CancellationToken token)
where TEntity : class
{
var tag = typeof(TEntity).Name;
return FromCacheAsync(query, tag, defaultObsolescence, token);
}
/// <summary>
/// Асинхронно кешировать запрос в List&lt;<typeparamref name="TEntity"/>&gt;.<br/>
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <param name="query"></param>
/// <param name="tag">Метка кеша</param>
/// <param name="obsolescence">Период устаревания данных</param>
/// <param name="token"></param>
/// <returns></returns>
public static async Task<IEnumerable<TEntity>> FromCacheAsync<TEntity>(this IQueryable<TEntity> query, string tag, TimeSpan obsolescence, CancellationToken token)
where TEntity : class
{
async Task<object[]> factory(CancellationToken token)
=> await query.AsNoTracking().ToArrayAsync(token);
var cache = await GetOrAddCacheAsync(tag, factory, obsolescence, token);
return cache.GetData<TEntity>();
}
/// <summary>
/// Асинхронно кешировать запрос с последующим преобразованием из <typeparamref name="TEntity"/> в <typeparamref name="TModel"/>.<br/>
/// Преобразование выполняется после получения из БД, результат кешируется в List&lt;<typeparamref name="TModel"/>&gt;.
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TModel"></typeparam>
/// <param name="query"></param>
/// <param name="tag">Метка кеша</param>
/// <param name="obsolescence">Период устаревания данных</param>
/// <param name="convert">Преобразование данных БД в DTO</param>
/// <param name="token"></param>
/// <returns></returns>
public static async Task<IEnumerable<TModel>> FromCacheAsync<TEntity, TModel>(this IQueryable<TEntity> query, string tag, TimeSpan obsolescence, Func<TEntity, TModel> convert, CancellationToken token)
where TEntity : class
{
async Task<object[]> factory(CancellationToken token)
=> await query.AsNoTracking().ToArrayAsync(token);
var cache = await GetOrAddCacheAsync(tag, factory, obsolescence, token);
return cache.GetData(convert);
}
/// <summary>
/// drops cache with tag = typeof(T).Name
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="query"></param>
public static void DropCache<T>(this IQueryable<T> query)
{
var tag = typeof(T).Name;
DropCache(query, tag);
}
/// <summary>
/// Очистить кеш
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="query"></param>
/// <param name="tag">Метка кеша</param>
public static void DropCache<T>(this IQueryable<T> query, string tag)
{
caches.Remove(tag, out var _);
}
}
#nullable disable
}

View File

@ -9,36 +9,121 @@ using System.Threading.Tasks;
namespace AsbCloudInfrastructure namespace AsbCloudInfrastructure
{ {
#nullable enable
public static class MemoryCacheExtentions public static class MemoryCacheExtentions
{ {
private static readonly TimeSpan CacheOlescence = TimeSpan.FromMinutes(5); private static readonly TimeSpan CacheOlescence = TimeSpan.FromMinutes(5);
/// <summary>
/// Создать кеш на основе асинхронного запроса к БД.
/// Ключ кеша - полное имя типа T.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="memoryCache"></param>
/// <param name="query"></param>
/// <param name="token"></param>
/// <returns></returns>
public static Task<IEnumerable<T>> GetOrCreateBasicAsync<T>(this IMemoryCache memoryCache, IQueryable<T> query, CancellationToken token)
where T : class
{
var getter = async (CancellationToken token) =>
{
var entities = await query
.ToArrayAsync(token);
return entities.AsEnumerable();
};
return memoryCache.GetOrCreateBasicAsync(getter, token);
}
[Obsolete(message: "use GetOrCreateBasicAsync<T>(this IMemoryCache memoryCache, IQueryable<T> query, CancellationToken token)")]
public static Task<IEnumerable<T>> GetOrCreateBasicAsync<T>(this IMemoryCache memoryCache, IAsbCloudDbContext dbContext, CancellationToken token) public static Task<IEnumerable<T>> GetOrCreateBasicAsync<T>(this IMemoryCache memoryCache, IAsbCloudDbContext dbContext, CancellationToken token)
where T : class where T : class
{ {
var cacheTag = typeof(T).FullName; var getter = async (CancellationToken token) =>
var cache = memoryCache.GetOrCreateAsync(cacheTag, async (cacheEntry) => { {
var entities = await dbContext.Set<T>()
.ToArrayAsync(token);
return entities.AsEnumerable();
};
return memoryCache.GetOrCreateBasicAsync(getter, token);
}
/// <summary>
/// Создать кеш на основе результата выполнения произвольной асинхронной функции.
/// Ключ кеша - полное имя типа T.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="memoryCache"></param>
/// <param name="getterAsync"></param>
/// <param name="token"></param>
/// <returns></returns>
public static Task<IEnumerable<T>> GetOrCreateBasicAsync<T>(this IMemoryCache memoryCache, Func<CancellationToken, Task<IEnumerable<T>>> getterAsync, CancellationToken token)
where T : class
{
var key = typeof(T).FullName;
var cache = memoryCache.GetOrCreateAsync(key, async (cacheEntry) => {
cacheEntry.AbsoluteExpirationRelativeToNow = CacheOlescence; cacheEntry.AbsoluteExpirationRelativeToNow = CacheOlescence;
cacheEntry.SlidingExpiration = CacheOlescence; cacheEntry.SlidingExpiration = CacheOlescence;
var entities = await getterAsync(token);
var entities = await dbContext.Set<T>().ToArrayAsync(token); return entities;
return entities.AsEnumerable();
}); });
return cache; return cache;
} }
/// <summary>
/// Создать кеш на основе запроса к БД.
/// Ключ кеша - полное имя типа T.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="memoryCache"></param>
/// <param name="query"></param>
/// <returns></returns>
public static IEnumerable<T> GetOrCreateBasic<T>(this IMemoryCache memoryCache, IQueryable<T> query)
where T : class
{
var getter = () => query.ToArray();
return memoryCache.GetOrCreateBasic(getter);
}
[Obsolete(message: "use GetOrCreateBasic<T>(this IMemoryCache memoryCache, IQueryable<T> query)")]
public static IEnumerable<T> GetOrCreateBasic<T>(this IMemoryCache memoryCache, IAsbCloudDbContext dbContext) public static IEnumerable<T> GetOrCreateBasic<T>(this IMemoryCache memoryCache, IAsbCloudDbContext dbContext)
where T : class where T : class
{ {
var cacheTag = typeof(T).FullName; var getter = () => dbContext.Set<T>().ToArray();
var cache = memoryCache.GetOrCreate(cacheTag, cacheEntry => { return memoryCache.GetOrCreateBasic(getter);
}
/// <summary>
/// Создать кеш на основе результата выполнения произвольной функции.
/// Ключ кеша - полное имя типа T.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="memoryCache"></param>
/// <param name="getter"></param>
/// <returns></returns>
public static IEnumerable<T> GetOrCreateBasic<T>(this IMemoryCache memoryCache, Func<IEnumerable<T>> getter)
where T : class
{
var key = typeof(T).FullName;
var cache = memoryCache.GetOrCreate(key, cacheEntry => {
cacheEntry.AbsoluteExpirationRelativeToNow = CacheOlescence; cacheEntry.AbsoluteExpirationRelativeToNow = CacheOlescence;
cacheEntry.SlidingExpiration = CacheOlescence; cacheEntry.SlidingExpiration = CacheOlescence;
return getter();
var entities = dbContext.Set<T>().ToArray();
return entities.AsEnumerable();
}); });
return cache; return cache;
} }
/// <summary>
/// Сбросить кеш.
/// Ключ кеша - полное имя типа T.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="memoryCache"></param>
public static void DropBasic<T>(this IMemoryCache memoryCache)
where T : class
{
var key = typeof(T).FullName;
memoryCache.Remove(key);
}
} }
} }

View File

@ -4,9 +4,9 @@ using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories; using AsbCloudApp.Repositories;
using AsbCloudDb; using AsbCloudDb;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudInfrastructure.EfCache;
using Mapster; using Mapster;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -19,14 +19,12 @@ namespace AsbCloudInfrastructure.Repository
public class UserRoleRepository : IUserRoleRepository public class UserRoleRepository : IUserRoleRepository
{ {
private readonly IAsbCloudDbContext dbContext; private readonly IAsbCloudDbContext dbContext;
private const string userRoleCacheTag = "UserRole"; private readonly IMemoryCache memoryCache;
private const string relationUserRoleUserRoleCacheTag = "RelationUserRoleUserRole";
private const string relationUserRolePermissionsCacheTag = "RelationUserRolePermissions";
private static readonly TimeSpan relationCacheObsolence = TimeSpan.FromMinutes(15);
public UserRoleRepository(IAsbCloudDbContext dbContext) public UserRoleRepository(IAsbCloudDbContext dbContext, IMemoryCache memoryCache)
{ {
this.dbContext = dbContext; this.dbContext = dbContext;
this.memoryCache = memoryCache;
} }
public async Task<int> InsertAsync(UserRoleDto dto, CancellationToken token) public async Task<int> InsertAsync(UserRoleDto dto, CancellationToken token)
@ -239,30 +237,30 @@ namespace AsbCloudInfrastructure.Repository
} }
private Task<IEnumerable<UserRole>> GetCacheUserRoleAsync(CancellationToken token) private Task<IEnumerable<UserRole>> GetCacheUserRoleAsync(CancellationToken token)
=> dbContext.UserRoles => memoryCache.GetOrCreateBasicAsync(dbContext.Set<UserRole>()
.Include(r => r.RelationUserRolePermissions) .Include(r => r.RelationUserRolePermissions)
.Include(r => r.RelationUserRoleUserRoles) .Include(r => r.RelationUserRoleUserRoles)
.Include(r => r.RelationUsersUserRoles) .Include(r => r.RelationUsersUserRoles), token);
.FromCacheAsync(userRoleCacheTag, relationCacheObsolence, token);
private IEnumerable<UserRole> GetCacheUserRole() private IEnumerable<UserRole> GetCacheUserRole()
=> dbContext.UserRoles => memoryCache.GetOrCreateBasic(dbContext.Set<UserRole>()
.Include(r => r.RelationUserRolePermissions) .Include(r => r.RelationUserRolePermissions)
.Include(r => r.RelationUserRoleUserRoles) .Include(r => r.RelationUserRoleUserRoles)
.Include(r => r.RelationUsersUserRoles) .Include(r => r.RelationUsersUserRoles));
.FromCache(userRoleCacheTag, relationCacheObsolence);
private void DropCacheUserRole() private void DropCacheUserRole()
=> dbContext.RelationUserUserRoles.DropCache(userRoleCacheTag); => memoryCache.DropBasic<UserRole>();
private void DropCacheRelationUserRoleUserRole() private void DropCacheRelationUserRoleUserRole()
=> dbContext.RelationUserUserRoles.DropCache(relationUserRoleUserRoleCacheTag); => memoryCache.DropBasic<RelationUserUserRole>();
private IEnumerable<RelationUserRolePermission> GetCacheRelationUserRolePermissions() private IEnumerable<RelationUserRolePermission> GetCacheRelationUserRolePermissions()
=> dbContext.RelationUserRolePermissions => memoryCache.GetOrCreateBasic(dbContext.Set<RelationUserRolePermission>()
.Include(r => r.UserRole) .Include(r => r.UserRole)
.Include(r => r.Permission) .Include(r => r.Permission));
.FromCache(relationUserRolePermissionsCacheTag, relationCacheObsolence);
private void DropCacheRelationUserRolePermissions() private void DropCacheRelationUserRolePermissions()
=> dbContext.RelationUserRolePermissions.DropCache(relationUserRolePermissionsCacheTag); => memoryCache.DropBasic<RelationUserRolePermission>();
private UserRoleDto Convert(UserRole entity) private UserRoleDto Convert(UserRole entity)
{ {

View File

@ -2,9 +2,10 @@
using AsbCloudApp.Exceptions; using AsbCloudApp.Exceptions;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudInfrastructure.EfCache;
using Mapster; using Mapster;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
@ -15,20 +16,28 @@ namespace AsbCloudInfrastructure.Services
public class MeasureService : IMeasureService public class MeasureService : IMeasureService
{ {
private readonly IAsbCloudDbContext db; private readonly IAsbCloudDbContext db;
private readonly IMemoryCache memoryCache;
private readonly IWellService wellService; private readonly IWellService wellService;
private static readonly System.TimeSpan cacheObsolescence = System.TimeSpan.FromMinutes(15); private static readonly TimeSpan CacheOlescence = TimeSpan.FromMinutes(20);
public MeasureService(IAsbCloudDbContext db, IWellService wellService) public MeasureService(IAsbCloudDbContext db, IMemoryCache memoryCache, IWellService wellService)
{ {
this.db = db; this.db = db;
this.memoryCache = memoryCache;
this.wellService = wellService; this.wellService = wellService;
} }
public async Task<Dictionary<int, string>> GetCategoriesAsync(CancellationToken token) public async Task<Dictionary<int, string>> GetCategoriesAsync(CancellationToken token)
{ {
var entities = await db.MeasureCategories.FromCacheAsync("MeasureCategories", cacheObsolescence, token).ConfigureAwait(false); var key = typeof(MeasureCategory).FullName;
var dto = entities.ToDictionary(e => e.Id, e => e.Name); var cache = await memoryCache.GetOrCreateAsync(key, async (cacheEntry) => {
return dto; cacheEntry.AbsoluteExpirationRelativeToNow = CacheOlescence;
cacheEntry.SlidingExpiration = CacheOlescence;
var entities = await db.Set<MeasureCategory>()
.ToDictionaryAsync(e => e.Id, e => e.Name, token);
return entities;
});
return cache;
} }
public async Task<MeasureDto> GetLastAsync(int idWell, int idCategory, CancellationToken token) public async Task<MeasureDto> GetLastAsync(int idWell, int idCategory, CancellationToken token)

View File

@ -2,7 +2,7 @@
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb; using AsbCloudDb;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudInfrastructure.EfCache; using Microsoft.Extensions.Caching.Memory;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
@ -13,11 +13,13 @@ namespace AsbCloudInfrastructure.Services.SAUB
public class EventService : IEventService public class EventService : IEventService
{ {
private readonly IAsbCloudDbContext db; private readonly IAsbCloudDbContext db;
private readonly IMemoryCache memoryCache;
private readonly ITelemetryService telemetryService; private readonly ITelemetryService telemetryService;
public EventService(IAsbCloudDbContext db, ITelemetryService telemetryService) public EventService(IAsbCloudDbContext db, IMemoryCache memoryCache, ITelemetryService telemetryService)
{ {
this.db = db; this.db = db;
this.memoryCache = memoryCache;
this.telemetryService = telemetryService; this.telemetryService = telemetryService;
} }
@ -37,7 +39,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
MessageTemplate = dto.Message MessageTemplate = dto.Message
}); });
var result = await db.Database.ExecInsertOrUpdateAsync(db.TelemetryEvents, entities, token); var result = await db.Database.ExecInsertOrUpdateAsync(db.TelemetryEvents, entities, token);
db.TelemetryEvents.DropCache(); memoryCache.DropBasic<TelemetryEvent>();
} }
} }
} }

View File

@ -4,9 +4,9 @@ using AsbCloudApp.Requests;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb; using AsbCloudDb;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudInfrastructure.EfCache;
using Mapster; using Mapster;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -19,11 +19,13 @@ namespace AsbCloudInfrastructure.Services.SAUB
public class MessageService : IMessageService public class MessageService : IMessageService
{ {
private readonly IAsbCloudDbContext db; private readonly IAsbCloudDbContext db;
private readonly IMemoryCache memoryCache;
private readonly ITelemetryService telemetryService; private readonly ITelemetryService telemetryService;
public MessageService(IAsbCloudDbContext db, ITelemetryService telemetryService) public MessageService(IAsbCloudDbContext db, IMemoryCache memoryCache, ITelemetryService telemetryService)
{ {
this.db = db; this.db = db;
this.memoryCache = memoryCache;
this.telemetryService = telemetryService; this.telemetryService = telemetryService;
} }
@ -39,7 +41,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
if (idTelemetry is null) if (idTelemetry is null)
return result; return result;
var allEvents = await db.TelemetryEvents.FromCacheAsync(token); var allEvents = await memoryCache.GetOrCreateBasicAsync(db.Set<TelemetryEvent>(), token);
var events = allEvents.Where(e => e.IdTelemetry == idTelemetry); var events = allEvents.Where(e => e.IdTelemetry == idTelemetry);
if (!events.Any()) if (!events.Any())
@ -93,7 +95,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
if (messagesList.Count == 0) if (messagesList.Count == 0)
return result; return result;
var allUsers = await db.TelemetryUsers.FromCacheAsync(token); var allUsers = await memoryCache.GetOrCreateBasicAsync(db.Set<TelemetryUser>(), token);
var users = allUsers.Where(u => u.IdTelemetry == idTelemetry); var users = allUsers.Where(u => u.IdTelemetry == idTelemetry);
var eventsDict = events.ToDictionary(x=>x.IdEvent, x => x); var eventsDict = events.ToDictionary(x=>x.IdEvent, x => x);

View File

@ -3,9 +3,9 @@ using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb; using AsbCloudDb;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudInfrastructure.EfCache;
using Mapster; using Mapster;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -17,10 +17,8 @@ namespace AsbCloudInfrastructure.Services.SAUB
#nullable enable #nullable enable
public class TelemetryService : ITelemetryService public class TelemetryService : ITelemetryService
{ {
private const string CacheTag = "TelemetryCache";
private static readonly TimeSpan telemetryCacheObsolescence = TimeSpan.FromMinutes(5);
private readonly IAsbCloudDbContext db; private readonly IAsbCloudDbContext db;
private readonly IMemoryCache memoryCache;
private readonly ITelemetryTracker telemetryTracker; private readonly ITelemetryTracker telemetryTracker;
private readonly ITimezoneService timezoneService; private readonly ITimezoneService timezoneService;
@ -29,25 +27,27 @@ namespace AsbCloudInfrastructure.Services.SAUB
public TelemetryService( public TelemetryService(
IAsbCloudDbContext db, IAsbCloudDbContext db,
IMemoryCache memoryCache,
ITelemetryTracker telemetryTracker, ITelemetryTracker telemetryTracker,
ITimezoneService timezoneService) ITimezoneService timezoneService)
{ {
this.db = db; this.db = db;
this.memoryCache = memoryCache;
this.telemetryTracker = telemetryTracker; this.telemetryTracker = telemetryTracker;
this.timezoneService = timezoneService; this.timezoneService = timezoneService;
} }
private IEnumerable<Telemetry> GetTelemetryCache() private IEnumerable<Telemetry> GetTelemetryCache()
{ {
var cache = db.Set<Telemetry>() var getter = () => db.Set<Telemetry>()
.Include(t => t.Well) .Include(t => t.Well)
.FromCache(CacheTag, telemetryCacheObsolescence); .ToArray();
return cache; return memoryCache.GetOrCreateBasic(getter);
} }
private void DropTelemetryCache() private void DropTelemetryCache()
{ {
db.Telemetries.DropCache(CacheTag); memoryCache.DropBasic<Telemetry>();
} }
public DateTime GetLastTelemetryDate(int idTelemetry) public DateTime GetLastTelemetryDate(int idTelemetry)
@ -228,9 +228,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
catch (Exception ex) catch (Exception ex)
{ {
System.Diagnostics.Trace.WriteLine($"Merge() Fail. Rollback. Reason is:{ex.Message}"); System.Diagnostics.Trace.WriteLine($"Merge() Fail. Rollback. Reason is:{ex.Message}");
#pragma warning disable CA2016 // Перенаправьте параметр "CancellationToken" в методы await transaction.RollbackAsync(CancellationToken.None);
await transaction.RollbackAsync();
#pragma warning restore CA2016 // Перенаправьте параметр "CancellationToken" в методы
return -1; return -1;
} }
} }

View File

@ -47,7 +47,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
.Options; .Options;
var db = new AsbCloudDbContext(contextOptions); var db = new AsbCloudDbContext(contextOptions);
var cacheTelemetry = memoryCache.GetOrCreateBasic<Telemetry>(db); var cacheTelemetry = memoryCache.GetOrCreateBasic(db.Set<Telemetry>());
var keyValuePairs = new Dictionary<string, TrackerStat>(cacheTelemetry.Count()); var keyValuePairs = new Dictionary<string, TrackerStat>(cacheTelemetry.Count());
foreach (var telemetry in cacheTelemetry) foreach (var telemetry in cacheTelemetry)
{ {

View File

@ -4,7 +4,6 @@ using AsbCloudApp.Repositories;
using AsbCloudApp.Requests; using AsbCloudApp.Requests;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudInfrastructure.EfCache;
using AsbCloudInfrastructure.Repository; using AsbCloudInfrastructure.Repository;
using Mapster; using Mapster;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -20,9 +19,6 @@ namespace AsbCloudInfrastructure.Services
#nullable enable #nullable enable
public class WellService : CrudCacheRepositoryBase<WellDto, Well>, IWellService public class WellService : CrudCacheRepositoryBase<WellDto, Well>, IWellService
{ {
private const string relationCompaniesWellsCacheTag = "RelationCompaniesWells";
private static readonly TimeSpan relationCompaniesWellsCacheObsolence = TimeSpan.FromMinutes(15);
private readonly ITelemetryService telemetryService; private readonly ITelemetryService telemetryService;
private readonly ICrudRepository<CompanyTypeDto> companyTypesService; private readonly ICrudRepository<CompanyTypeDto> companyTypesService;
private readonly ITimezoneService timezoneService; private readonly ITimezoneService timezoneService;
@ -50,13 +46,18 @@ namespace AsbCloudInfrastructure.Services
} }
private Task<IEnumerable<RelationCompanyWell>> GetCacheRelationCompanyWellAsync(CancellationToken token) private Task<IEnumerable<RelationCompanyWell>> GetCacheRelationCompanyWellAsync(CancellationToken token)
=> dbContext.RelationCompaniesWells {
.Include(r => r.Company) var getter = async (CancellationToken token)
.Include(r => r.Well) => (await dbContext.Set<RelationCompanyWell>()
.FromCacheAsync(relationCompaniesWellsCacheTag, relationCompaniesWellsCacheObsolence, token); .Include(r => r.Company)
.Include(r => r.Well)
.ToArrayAsync(token))
.AsEnumerable();
return memoryCache.GetOrCreateBasicAsync(getter, token);
}
private void DropCacheRelationCompanyWell() private void DropCacheRelationCompanyWell()
=> dbContext.RelationCompaniesWells.DropCache(relationCompaniesWellsCacheTag); => memoryCache.DropBasic<RelationCompanyWell>();
public DateTimeOffset GetLastTelemetryDate(int idWell) public DateTimeOffset GetLastTelemetryDate(int idWell)
{ {