forked from ddrilling/AsbCloudServer
Converted cache
This commit is contained in:
parent
cb4eb3341c
commit
b89ea13c78
332
AsbCloudInfrastructure/EfCache/EfCacheExtensions.cs
Normal file
332
AsbCloudInfrastructure/EfCache/EfCacheExtensions.cs
Normal file
@ -0,0 +1,332 @@
|
||||
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 class CacheItem
|
||||
{
|
||||
internal IEnumerable? Data;
|
||||
internal DateTime DateObsolete;
|
||||
internal DateTime DateObsoleteTotal;
|
||||
internal readonly SemaphoreSlim semaphore = new(1);
|
||||
}
|
||||
|
||||
private static CacheItem GetOrAddCache(string tag, Func<IEnumerable> valueFactory, TimeSpan obsolete)
|
||||
{
|
||||
CacheItem cache;
|
||||
while (!caches.ContainsKey(tag))
|
||||
{
|
||||
if (semaphore.Wait(0))
|
||||
{
|
||||
try {
|
||||
cache = new CacheItem();
|
||||
|
||||
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;
|
||||
caches.Add(tag, cache);
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
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;
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
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<IEnumerable>> valueFactoryAsync, TimeSpan obsolete, CancellationToken token)
|
||||
{
|
||||
CacheItem cache;
|
||||
while (!caches.ContainsKey(tag))
|
||||
{
|
||||
if (semaphore.Wait(0))
|
||||
{
|
||||
try
|
||||
{
|
||||
cache = new CacheItem();
|
||||
|
||||
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;
|
||||
caches.Add(tag, cache);
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
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;
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
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<<typeparamref name="TEntity"/>>.
|
||||
/// </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
|
||||
{
|
||||
IEnumerable factory()
|
||||
{
|
||||
var queryData = query.AsNoTracking()
|
||||
.ToList();
|
||||
return queryData;
|
||||
}
|
||||
var cache = GetOrAddCache(tag, factory, obsolescence);
|
||||
if(cache.Data is IEnumerable<TEntity> typedData)
|
||||
return typedData;
|
||||
throw new TypeAccessException("Cache data has wrong type. Possible 'tag' is not unique.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Кешировать запрос с последующим преобразованием из <typeparamref name="TEntity"/> в <typeparamref name="TModel"/>.<br/>
|
||||
/// Преобразование выполняется после получения из БД, результат кешируется в List<<typeparamref name="TEntity"/>>.
|
||||
/// </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
|
||||
{
|
||||
IEnumerable factory ()
|
||||
{
|
||||
var queryData = query.AsNoTracking()
|
||||
.ToList();
|
||||
var data = queryData
|
||||
.Select(convert)
|
||||
.ToList();
|
||||
return data;
|
||||
}
|
||||
var cache = GetOrAddCache(tag, factory, obsolescence);
|
||||
if (cache.Data is IEnumerable<TModel> typedData)
|
||||
return typedData;
|
||||
throw new TypeAccessException("Cache data has wrong type. Possible 'tag' is not unique.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Асинхронно кешировать запрос в List<<typeparamref name="TEntity"/>>.<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 = default)
|
||||
where TEntity : class
|
||||
{
|
||||
async Task<IEnumerable> factory(CancellationToken token)
|
||||
{
|
||||
var queryData = await query.AsNoTracking().ToListAsync(token);
|
||||
return queryData;
|
||||
}
|
||||
var cache = await GetOrAddCacheAsync(tag, factory, obsolescence, token);
|
||||
if (cache.Data is IEnumerable<TEntity> typedData)
|
||||
return typedData;
|
||||
throw new TypeAccessException("Cache data has wrong type. Possible 'tag' is not unique.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Асинхронно кешировать запрос с последующим преобразованием из <typeparamref name="TEntity"/> в <typeparamref name="TModel"/>.<br/>
|
||||
/// Преобразование выполняется после получения из БД, результат кешируется в List<<typeparamref name="TModel"/>>.
|
||||
/// </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 = default)
|
||||
where TEntity : class
|
||||
{
|
||||
async Task<IEnumerable> factory(CancellationToken token)
|
||||
{
|
||||
var queryData = await query.AsNoTracking().ToListAsync(token);
|
||||
var data = queryData
|
||||
.Select(convert)
|
||||
.ToList();
|
||||
return data;
|
||||
}
|
||||
var cache = await GetOrAddCacheAsync(tag, factory, obsolescence, token);
|
||||
if (cache.Data is IEnumerable<TModel> typedData)
|
||||
return typedData;
|
||||
throw new TypeAccessException("Cache data has wrong type. Possible 'tag' is not unique.");
|
||||
}
|
||||
|
||||
/// <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
|
||||
}
|
358
AsbCloudInfrastructure/EfCache/EfCacheWithKeyExtensions.cs
Normal file
358
AsbCloudInfrastructure/EfCache/EfCacheWithKeyExtensions.cs
Normal file
@ -0,0 +1,358 @@
|
||||
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.
|
||||
/// Кеш не отслеживается ChangeTracker.
|
||||
/// </summary>
|
||||
public static class EfCacheDictionaryExtensions
|
||||
{
|
||||
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 class CacheItem
|
||||
{
|
||||
internal IDictionary? Data;
|
||||
internal DateTime DateObsolete;
|
||||
internal DateTime DateObsoleteTotal;
|
||||
internal readonly SemaphoreSlim semaphore = new(1);
|
||||
}
|
||||
|
||||
private static CacheItem GetOrAddCache(string tag, Func<IDictionary> valueFactory, TimeSpan obsolete)
|
||||
{
|
||||
CacheItem cache;
|
||||
while (!caches.ContainsKey(tag))
|
||||
{
|
||||
if (semaphore.Wait(0))
|
||||
{
|
||||
try
|
||||
{
|
||||
cache = new CacheItem();
|
||||
|
||||
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;
|
||||
caches.Add(tag, cache);
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
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;
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
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<IDictionary>> valueFactoryAsync, TimeSpan obsolete, CancellationToken token)
|
||||
{
|
||||
CacheItem cache;
|
||||
while (!caches.ContainsKey(tag))
|
||||
{
|
||||
if (semaphore.Wait(0))
|
||||
{
|
||||
try
|
||||
{
|
||||
cache = new CacheItem();
|
||||
|
||||
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;
|
||||
caches.Add(tag, cache);
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
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;
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
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>
|
||||
/// Кешировать запрос в Dictionary<<typeparamref name="TKey"/>, <typeparamref name="TEntity"/>>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">тип ключа</typeparam>
|
||||
/// <typeparam name="TEntity">тип значения</typeparam>
|
||||
/// <param name="query"></param>
|
||||
/// <param name="tag">Метка кеша</param>
|
||||
/// <param name="obsolescence">Период устаревания данных</param>
|
||||
/// <param name="keySelector">Делегат получения ключа из записи</param>
|
||||
/// <returns></returns>
|
||||
public static Dictionary<TKey, TEntity> FromCacheDictionary<TKey, TEntity>(
|
||||
this IQueryable<TEntity> query,
|
||||
string tag,
|
||||
TimeSpan obsolescence,
|
||||
Func<TEntity, TKey> keySelector)
|
||||
where TEntity : class
|
||||
where TKey : notnull
|
||||
{
|
||||
IDictionary factory()
|
||||
{
|
||||
var queryData = query.AsNoTracking()
|
||||
.ToDictionary(keySelector);
|
||||
return queryData;
|
||||
}
|
||||
var cache = GetOrAddCache(tag, factory, obsolescence);
|
||||
if (cache.Data is Dictionary<TKey, TEntity> typedData)
|
||||
return typedData;
|
||||
throw new TypeAccessException("Cache data has wrong type. Possible 'tag' is not unique.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Кешировать запрос с последующим преобразованием из <typeparamref name="TEntity"/> в <typeparamref name="TModel"/>.<br/>
|
||||
/// Преобразование выполняется после получения из БД, результат кешируется в Dictionary<<typeparamref name="TKey"/>, <typeparamref name="TModel"/>>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">тип ключа</typeparam>
|
||||
/// <typeparam name="TEntity">тип значения</typeparam>
|
||||
/// <typeparam name="TModel"></typeparam>
|
||||
/// <param name="query"></param>
|
||||
/// <param name="tag">Метка кеша</param>
|
||||
/// <param name="obsolescence">Период устаревания данных</param>
|
||||
/// <param name="keySelector">Делегат получения ключа из записи</param>
|
||||
/// <param name="convert">Преобразование данных БД в DTO</param>
|
||||
/// <returns></returns>
|
||||
public static Dictionary<TKey, TModel> FromCacheDictionary<TKey, TEntity, TModel>(
|
||||
this IQueryable<TEntity> query,
|
||||
string tag,
|
||||
TimeSpan obsolescence,
|
||||
Func<TEntity, TKey> keySelector,
|
||||
Func<TEntity, TModel> convert)
|
||||
where TEntity : class
|
||||
where TKey : notnull
|
||||
{
|
||||
IDictionary factory()
|
||||
{
|
||||
var queryData = query.AsNoTracking()
|
||||
.ToList();
|
||||
var data = queryData
|
||||
.ToDictionary(keySelector, convert);
|
||||
return data;
|
||||
}
|
||||
var cache = GetOrAddCache(tag, factory, obsolescence);
|
||||
if (cache.Data is Dictionary<TKey, TModel> typedData)
|
||||
return typedData;
|
||||
throw new TypeAccessException("Cache data has wrong type. Possible 'tag' is not unique.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Асинхронно кешировать запрос в Dictionary<<typeparamref name="TKey"/>, <typeparamref name="TEntity"/>>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">тип ключа</typeparam>
|
||||
/// <typeparam name="TEntity">тип значения</typeparam>
|
||||
/// <param name="query"></param>
|
||||
/// <param name="tag">Метка кеша</param>
|
||||
/// <param name="obsolescence">Период устаревания данных</param>
|
||||
/// <param name="keySelector">Делегат получения ключа из записи</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<Dictionary<TKey, TEntity>> FromCacheDictionaryAsync<TKey, TEntity>(
|
||||
this IQueryable<TEntity> query,
|
||||
string tag,
|
||||
TimeSpan obsolescence,
|
||||
Func<TEntity, TKey> keySelector,
|
||||
CancellationToken token = default)
|
||||
where TEntity : class
|
||||
where TKey : notnull
|
||||
{
|
||||
async Task<IDictionary> factory(CancellationToken token)
|
||||
{
|
||||
var queryData = await query.AsNoTracking().ToDictionaryAsync(keySelector, token);
|
||||
return queryData;
|
||||
}
|
||||
var cache = await GetOrAddCacheAsync(tag, factory, obsolescence, token);
|
||||
|
||||
if (cache.Data is Dictionary<TKey, TEntity> typedData)
|
||||
return typedData;
|
||||
throw new TypeAccessException("Cache data has wrong type. Possible 'tag' is not unique.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Асинхронно кешировать запрос с последующим преобразованием из <typeparamref name="TEntity"/> в <typeparamref name="TModel"/>.<br/>
|
||||
/// Преобразование выполняется после получения из БД, результат кешируется в Dictionary<<typeparamref name="TKey"/>, <typeparamref name="TModel"/>>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">тип ключа</typeparam>
|
||||
/// <typeparam name="TEntity">тип значения</typeparam>
|
||||
/// <typeparam name="TModel"></typeparam>
|
||||
/// <param name="query"></param>
|
||||
/// <param name="tag">Метка кеша</param>
|
||||
/// <param name="obsolescence">Период устаревания данных</param>
|
||||
/// <param name="keySelector">Делегат получения ключа из записи</param>
|
||||
/// <param name="convert">Преобразование данных БД в DTO</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<Dictionary<TKey, TModel>> FromCacheDictionaryAsync<TKey, TEntity, TModel>(this IQueryable<TEntity> query, string tag, TimeSpan obsolescence, Func<TEntity, TKey> keySelector, Func<TEntity, TModel> convert, CancellationToken token = default)
|
||||
where TEntity : class
|
||||
where TKey : notnull
|
||||
{
|
||||
async Task<IDictionary> factory(CancellationToken token)
|
||||
{
|
||||
var queryData = await query.AsNoTracking().ToListAsync(token);
|
||||
var data = queryData.ToDictionary(keySelector, convert);
|
||||
return data;
|
||||
}
|
||||
var cache = await GetOrAddCacheAsync(tag, factory, obsolescence, token);
|
||||
|
||||
if (cache.Data is Dictionary<TKey, TModel> typedData)
|
||||
return typedData;
|
||||
throw new TypeAccessException("Cache data has wrong type. Possible 'tag' is not unique.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Очистить кеш
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="query"></param>
|
||||
/// <param name="tag">Метка кеша</param>
|
||||
public static void DropCacheDictionary<T>(this IQueryable<T> query, string tag)
|
||||
{
|
||||
caches.Remove(tag, out var _);
|
||||
}
|
||||
}
|
||||
#nullable disable
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
using AsbCloudApp.Data;
|
||||
using AsbCloudDb.Model;
|
||||
using AsbCloudInfrastructure.Services.Cache;
|
||||
using AsbCloudInfrastructure.EfCache;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -22,9 +22,9 @@ namespace ConsoleApp1
|
||||
for (int i = 0; i < 24; i++)
|
||||
{
|
||||
var t = new Thread(_ => {
|
||||
for (int j = 0; j < 64; j++)
|
||||
Task.Run(GetClastersAsync).Wait();
|
||||
//GetClasters();
|
||||
for (int j = 0; j < 32; j++)
|
||||
//Task.Run(GetDataAsync).Wait();
|
||||
GetData();
|
||||
});
|
||||
t.Start();
|
||||
}
|
||||
@ -34,7 +34,7 @@ namespace ConsoleApp1
|
||||
}
|
||||
|
||||
static TimeSpan obso = TimeSpan.FromSeconds(5);
|
||||
static (long, long) GetClasters()
|
||||
static (long, long) GetData()
|
||||
{
|
||||
using var db = ServiceFactory.MakeContext();
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
@ -42,10 +42,9 @@ namespace ConsoleApp1
|
||||
.Where(t => t.IdTelemetry == 135)
|
||||
.OrderBy(t => t.DateTime)
|
||||
.Take(100_000)
|
||||
.FromCache("tds", obso)
|
||||
.FromCache("tds", obso, r => new { r.Pressure, r.HookWeight })
|
||||
.ToList();
|
||||
sw.Stop();
|
||||
//Console.WriteLine($"{DateTime.Now}\tth: {Thread.CurrentThread.ManagedThreadId}\trequests {EfCacheL2rev4.RequestsToDb}\ttime {sw.ElapsedMilliseconds}\tcount {cs.Count}");
|
||||
Console.WriteLine($"{DateTime.Now}\tth: {Thread.CurrentThread.ManagedThreadId}\ttime {sw.ElapsedMilliseconds}\tcount {cs.Count}");
|
||||
|
||||
GC.Collect();
|
||||
@ -53,18 +52,17 @@ namespace ConsoleApp1
|
||||
return (cs.Count, sw.ElapsedMilliseconds);
|
||||
}
|
||||
|
||||
static async Task<(long, long)> GetClastersAsync()
|
||||
static async Task<(long, long)> GetDataAsync()
|
||||
{
|
||||
using var db = ServiceFactory.MakeContext();
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
var cs = ( await db.TelemetryDataSaub
|
||||
var cs = (await db.TelemetryDataSaub
|
||||
.Where(t => t.IdTelemetry == 135)
|
||||
.OrderBy(t => t.DateTime)
|
||||
.Take(100_000)
|
||||
.FromCacheAsync("tds", obso, r => (r.IdTelemetry, r.DateTime)))
|
||||
.FromCacheDictionaryAsync("tds", obso, r => (r.IdTelemetry, r.DateTime), r => new {r.Pressure, r.HookWeight }))
|
||||
.ToList();
|
||||
sw.Stop();
|
||||
//Console.WriteLine($"{DateTime.Now}\tth: {Thread.CurrentThread.ManagedThreadId}\trequests {EfCacheL2rev4.RequestsToDb}\ttime {sw.ElapsedMilliseconds}\tcount {cs.Count}");
|
||||
Console.WriteLine($"{DateTime.Now}\tth: {Thread.CurrentThread.ManagedThreadId}\ttime {sw.ElapsedMilliseconds}\tcount {cs.Count}");
|
||||
|
||||
GC.Collect();
|
||||
|
Loading…
Reference in New Issue
Block a user