forked from ddrilling/AsbCloudServer
EfCacheL2 tested.
This commit is contained in:
parent
fa38c145e7
commit
cb4eb3341c
@ -1,6 +1,6 @@
|
|||||||
using System;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@ -9,44 +9,67 @@ using System.Threading.Tasks;
|
|||||||
namespace AsbCloudInfrastructure.Services.Cache
|
namespace AsbCloudInfrastructure.Services.Cache
|
||||||
{
|
{
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
/// <summary>
|
||||||
|
/// Кеширование запросов EF.
|
||||||
|
/// </summary>
|
||||||
public static class EfCacheL2
|
public static class EfCacheL2
|
||||||
{
|
{
|
||||||
public static int RequestsToDb = 0;
|
/// <summary>
|
||||||
|
/// Кол-во обращений к БД.
|
||||||
static readonly ConcurrentDictionary<string, Lazy<CacheItem>> caches = new(1, 16);
|
/// </summary>
|
||||||
|
public static int CountOfRequestsToDB = 0;
|
||||||
private const int semaphoreTimeout = 25_000;
|
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 SemaphoreSlim semaphore = new(1);
|
||||||
|
private static readonly TimeSpan minCacheTime = TimeSpan.FromSeconds(2);
|
||||||
|
|
||||||
private struct CacheItem
|
private class CacheItem
|
||||||
{
|
{
|
||||||
public readonly IEnumerable Data;
|
internal IEnumerable? Data;
|
||||||
public readonly DateTime DateObsolete;
|
internal DateTime DateObsolete;
|
||||||
|
internal DateTime DateObsoleteTotal;
|
||||||
public CacheItem(IEnumerable data, TimeSpan obsolescence)
|
internal readonly SemaphoreSlim semaphore = new(1);
|
||||||
{
|
|
||||||
DateObsolete = DateTime.Now + obsolescence;
|
|
||||||
Data = data;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CacheItem GetOrAddCache(string tag, Func<CacheItem> valueFactory)
|
private static CacheItem GetOrAddCache(string tag, Func<IEnumerable> valueFactory, TimeSpan obsolete)
|
||||||
{
|
{
|
||||||
Lazy<CacheItem>? lazyCache;
|
CacheItem cache;
|
||||||
while (!caches.TryGetValue(tag, out lazyCache))
|
while (!caches.ContainsKey(tag))
|
||||||
{
|
{
|
||||||
if (semaphore.Wait(0))
|
if (semaphore.Wait(0))
|
||||||
{
|
{
|
||||||
lazyCache = new Lazy<CacheItem>(valueFactory);
|
try {
|
||||||
caches.TryAdd(tag, lazyCache);
|
cache = new CacheItem();
|
||||||
_ = lazyCache.Value;
|
|
||||||
semaphore.Release();
|
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;
|
break;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if(semaphore.Wait(semaphoreTimeout))
|
if (semaphore.Wait(semaphoreTimeout))
|
||||||
|
{
|
||||||
semaphore.Release();
|
semaphore.Release();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
semaphore.Release();
|
semaphore.Release();
|
||||||
@ -55,55 +78,91 @@ namespace AsbCloudInfrastructure.Services.Cache
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lazyCache.Value.DateObsolete < DateTime.Now)
|
cache = caches[tag];
|
||||||
|
|
||||||
|
if (cache.DateObsolete < DateTime.Now)
|
||||||
{
|
{
|
||||||
var isUpdated = false;
|
if (cache.semaphore.Wait(0))
|
||||||
if (semaphore.Wait(0))
|
|
||||||
{
|
{
|
||||||
lazyCache = new Lazy<CacheItem>(valueFactory);
|
try
|
||||||
caches.Remove(tag, out _);
|
{
|
||||||
caches.TryAdd(tag, lazyCache);
|
var dateObsolete = DateTime.Now + obsolete;
|
||||||
_ = lazyCache.Value;
|
var dateQueryStart = DateTime.Now;
|
||||||
isUpdated = true;
|
var data = valueFactory();
|
||||||
semaphore.Release();
|
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
|
else if(cache.DateObsoleteTotal < DateTime.Now)
|
||||||
{
|
{
|
||||||
if (semaphore.Wait(semaphoreTimeout))
|
if (cache.semaphore.Wait(semaphoreTimeout))
|
||||||
semaphore.Release();
|
{
|
||||||
|
cache.semaphore.Release();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
semaphore.Release();
|
cache.semaphore.Release();
|
||||||
throw new TimeoutException("EfCacheL2.GetOrAddCache. Can't wait too long while getting cache");
|
throw new TimeoutException("EfCacheL2.GetOrAddCache. Can't wait too long while getting cache");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isUpdated || caches.TryGetValue(tag, out lazyCache))
|
|
||||||
return lazyCache.Value;
|
|
||||||
|
|
||||||
throw new Exception("EfCacheL2.GetOrAddCache it should never happens");
|
|
||||||
}
|
}
|
||||||
else
|
return cache;
|
||||||
return lazyCache.Value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<CacheItem> GetOrAddCacheAsync(string tag, Func<CacheItem> valueFactory, CancellationToken token)
|
private static async Task<CacheItem> GetOrAddCacheAsync(string tag, Func<CancellationToken, Task<IEnumerable>> valueFactoryAsync, TimeSpan obsolete, CancellationToken token)
|
||||||
{
|
{
|
||||||
Lazy<CacheItem>? lazyCache;
|
CacheItem cache;
|
||||||
while (!caches.TryGetValue(tag, out lazyCache))
|
while (!caches.ContainsKey(tag))
|
||||||
{
|
{
|
||||||
if (semaphore.Wait(0, CancellationToken.None))
|
if (semaphore.Wait(0))
|
||||||
{
|
{
|
||||||
lazyCache = new Lazy<CacheItem>(valueFactory);
|
try
|
||||||
caches.TryAdd(tag, lazyCache);
|
{
|
||||||
_ = lazyCache.Value;
|
cache = new CacheItem();
|
||||||
semaphore.Release();
|
|
||||||
|
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;
|
break;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (await semaphore.WaitAsync(semaphoreTimeout, token))
|
if (await semaphore.WaitAsync(semaphoreTimeout, token))
|
||||||
|
{
|
||||||
semaphore.Release();
|
semaphore.Release();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
semaphore.Release();
|
semaphore.Release();
|
||||||
@ -112,39 +171,52 @@ namespace AsbCloudInfrastructure.Services.Cache
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lazyCache.Value.DateObsolete < DateTime.Now)
|
cache = caches[tag];
|
||||||
|
|
||||||
|
if (cache.DateObsolete < DateTime.Now)
|
||||||
{
|
{
|
||||||
var isUpdated = false;
|
if (cache.semaphore.Wait(0))
|
||||||
if (semaphore.Wait(0, CancellationToken.None))
|
|
||||||
{
|
{
|
||||||
lazyCache = new Lazy<CacheItem>(valueFactory);
|
try
|
||||||
caches.Remove(tag, out _);
|
|
||||||
caches.TryAdd(tag, lazyCache);
|
|
||||||
_ = lazyCache.Value;
|
|
||||||
isUpdated = true;
|
|
||||||
semaphore.Release();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (await semaphore.WaitAsync(semaphoreTimeout, token))
|
|
||||||
semaphore.Release();
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
semaphore.Release();
|
var dateObsolete = DateTime.Now + obsolete;
|
||||||
throw new TimeoutException("EfCacheL2.GetOrAddCache. Can't wait too long while getting cache");
|
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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isUpdated || caches.TryGetValue(tag, out lazyCache))
|
|
||||||
return lazyCache.Value;
|
|
||||||
|
|
||||||
throw new Exception("EfCacheL2.GetOrAddCache it should never happens");
|
|
||||||
}
|
}
|
||||||
else
|
return cache;
|
||||||
return lazyCache.Value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<T> ConvertToIEnumerable<T>(IEnumerable data)
|
private static IEnumerable<T> ConvertToIEnumerable<T>(IEnumerable? data)
|
||||||
{
|
{
|
||||||
if (data is IEnumerable<T> list)
|
if (data is IEnumerable<T> list)
|
||||||
return list;
|
return list;
|
||||||
@ -157,84 +229,177 @@ namespace AsbCloudInfrastructure.Services.Cache
|
|||||||
throw new NotSupportedException("cache.Data has wrong type.");
|
throw new NotSupportedException("cache.Data has wrong type.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Dictionary<TKey, T> ConvertToDictionary<TKey, T>(IEnumerable data, Func<T, TKey> keySelector)
|
private static Dictionary<TKey, T> ConvertToDictionary<TKey, T>(IEnumerable? data, Func<T, TKey> keySelector)
|
||||||
where TKey : notnull
|
where TKey : notnull
|
||||||
{
|
{
|
||||||
if (data is Dictionary<TKey, T> dictionary)
|
if (data is Dictionary<TKey, T> dictionary)
|
||||||
return dictionary;
|
return dictionary;
|
||||||
else
|
else if (data is IEnumerable<T> enumerable)
|
||||||
{
|
{
|
||||||
System.Diagnostics.Trace.TraceWarning($"ConvertToDictionary. Use keyed method on keyless cache. Type: {typeof(T).Name};");
|
System.Diagnostics.Trace.TraceWarning($"ConvertToDictionary. Use keyed method on keyless cache. Type: {typeof(T).Name};");
|
||||||
return ((IEnumerable<T>)data).ToDictionary(keySelector);
|
return enumerable.ToDictionary(keySelector);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
throw new NotSupportedException("cache.Data has wrong type.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Func<CacheItem> MakeValueListFactory<T>(IQueryable<T> query, TimeSpan obsolescence)
|
/// <summary>
|
||||||
{
|
/// Кешировать запрос в List\<typeparamref name="T"\>.
|
||||||
CacheItem ValueFactory()
|
/// Выборки по PK будут работать медленнее, чем при кешировании в виде словаря.
|
||||||
{
|
/// </summary>
|
||||||
var list = query.ToList();
|
/// <typeparam name="T"></typeparam>
|
||||||
RequestsToDb++;
|
/// <param name="query"></param>
|
||||||
return new CacheItem(list, obsolescence);
|
/// <param name="tag">Метка кеша</param>
|
||||||
}
|
/// <param name="obsolescence">Период устаревания данных</param>
|
||||||
return ValueFactory;
|
/// <returns></returns>
|
||||||
}
|
|
||||||
|
|
||||||
private static Func<CacheItem> MakeValueDictionaryFactory<TKey, T>(IQueryable<T> query, TimeSpan obsolescence, Func<T, TKey> keySelector)
|
|
||||||
where TKey : notnull
|
|
||||||
{
|
|
||||||
CacheItem ValueFactory()
|
|
||||||
{
|
|
||||||
var dictionary = query.ToDictionary(keySelector);
|
|
||||||
RequestsToDb++;
|
|
||||||
return new CacheItem(dictionary, obsolescence);
|
|
||||||
};
|
|
||||||
return ValueFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<T> FromCache<T>(this IQueryable<T> query, string tag, TimeSpan obsolescence)
|
public static IEnumerable<T> FromCache<T>(this IQueryable<T> query, string tag, TimeSpan obsolescence)
|
||||||
{
|
{
|
||||||
var factory = MakeValueListFactory(query, obsolescence);
|
IEnumerable factory ()
|
||||||
var cache = GetOrAddCache(tag, factory);
|
{
|
||||||
|
CountOfRequestsToDB++;
|
||||||
|
return query.ToList();
|
||||||
|
}
|
||||||
|
var cache = GetOrAddCache(tag, factory, obsolescence);
|
||||||
return ConvertToIEnumerable<T>(cache.Data);
|
return ConvertToIEnumerable<T>(cache.Data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Асинхронно кешировать запрос в List\<typeparamref name="T"\>.
|
||||||
|
/// Выборки по PK будут работать медленнее, чем при кешировании в виде словаря.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="query"></param>
|
||||||
|
/// <param name="tag">Метка кеша</param>
|
||||||
|
/// <param name="obsolescence">Период устаревания данных</param>
|
||||||
|
/// <param name="token"></param>
|
||||||
|
/// <returns></returns>
|
||||||
public static async Task<IEnumerable<T>> FromCacheAsync<T>(this IQueryable<T> query, string tag, TimeSpan obsolescence, CancellationToken token = default)
|
public static async Task<IEnumerable<T>> FromCacheAsync<T>(this IQueryable<T> query, string tag, TimeSpan obsolescence, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
var factory = MakeValueListFactory(query, obsolescence);
|
async Task<IEnumerable> factory(CancellationToken token)
|
||||||
var cache = await GetOrAddCacheAsync(tag, factory, token);
|
{
|
||||||
|
CountOfRequestsToDB++;
|
||||||
|
return await query.ToListAsync(token);
|
||||||
|
}
|
||||||
|
var cache = await GetOrAddCacheAsync(tag, factory, obsolescence, token);
|
||||||
return ConvertToIEnumerable<T>(cache.Data);
|
return ConvertToIEnumerable<T>(cache.Data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Кешировать запрос в Dictionary\<typeparamref name="TKey", typeparamref name="T"\>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TKey">тип ключа</typeparam>
|
||||||
|
/// <typeparam name="T">тип значения</typeparam>
|
||||||
|
/// <param name="query"></param>
|
||||||
|
/// <param name="tag">Метка кеша</param>
|
||||||
|
/// <param name="obsolescence">Период устаревания данных</param>
|
||||||
|
/// <param name="keySelector">Делегат получения ключа из записи</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <example>
|
||||||
|
///
|
||||||
|
/// </example>
|
||||||
public static Dictionary<TKey, T> FromCache<TKey, T>(this IQueryable<T> query, string tag, TimeSpan obsolescence, Func<T, TKey> keySelector)
|
public static Dictionary<TKey, T> FromCache<TKey, T>(this IQueryable<T> query, string tag, TimeSpan obsolescence, Func<T, TKey> keySelector)
|
||||||
where TKey: notnull
|
where TKey: notnull
|
||||||
{
|
{
|
||||||
var factory = MakeValueDictionaryFactory(query, obsolescence, keySelector);
|
IEnumerable factory ()
|
||||||
var cache = GetOrAddCache(tag, factory);
|
{
|
||||||
|
CountOfRequestsToDB++;
|
||||||
|
return query.ToDictionary(keySelector);
|
||||||
|
}
|
||||||
|
var cache = GetOrAddCache(tag, factory, obsolescence);
|
||||||
return ConvertToDictionary(cache.Data, keySelector);
|
return ConvertToDictionary(cache.Data, keySelector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Асинхронно кешировать запрос в Dictionary\<typeparamref name="TKey", typeparamref name="T"\>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TKey">тип ключа</typeparam>
|
||||||
|
/// <typeparam name="T">тип значения</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, T>> FromCacheAsync<TKey, T>(this IQueryable<T> query, string tag, TimeSpan obsolescence, Func<T, TKey> keySelector, CancellationToken token = default)
|
public static async Task<Dictionary<TKey, T>> FromCacheAsync<TKey, T>(this IQueryable<T> query, string tag, TimeSpan obsolescence, Func<T, TKey> keySelector, CancellationToken token = default)
|
||||||
where TKey : notnull
|
where TKey : notnull
|
||||||
{
|
{
|
||||||
var factory = MakeValueDictionaryFactory(query, obsolescence, keySelector);
|
async Task<IEnumerable> factory(CancellationToken token)
|
||||||
var cache = await GetOrAddCacheAsync(tag, factory, token);
|
{
|
||||||
|
CountOfRequestsToDB++;
|
||||||
|
return await query.ToDictionaryAsync(keySelector, token);
|
||||||
|
}
|
||||||
|
var cache = await GetOrAddCacheAsync(tag, factory, obsolescence, token);
|
||||||
return ConvertToDictionary(cache.Data, keySelector);
|
return ConvertToDictionary(cache.Data, keySelector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получить запись из кеша по ключу.
|
||||||
|
/// При отсутствии кеша создаст его для всех записей из query.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TKey">тип ключа</typeparam>
|
||||||
|
/// <typeparam name="T">тип значения</typeparam>
|
||||||
|
/// <param name="query"></param>
|
||||||
|
/// <param name="tag">Метка кеша</param>
|
||||||
|
/// <param name="obsolescence">Период устаревания данных</param>
|
||||||
|
/// <param name="keySelector">Делегат получения ключа из записи</param>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="NotSupportedException">if cache contains trash</exception>
|
||||||
public static T? FromCacheGetValueOrDefault<TKey, T>(this IQueryable<T> query, string tag, TimeSpan obsolescence, Func<T, TKey> keySelector, TKey key)
|
public static T? FromCacheGetValueOrDefault<TKey, T>(this IQueryable<T> query, string tag, TimeSpan obsolescence, Func<T, TKey> keySelector, TKey key)
|
||||||
where TKey : notnull
|
where TKey : notnull
|
||||||
{
|
{
|
||||||
var factory = MakeValueDictionaryFactory(query, obsolescence, keySelector);
|
IEnumerable factory()
|
||||||
var cache = GetOrAddCache(tag, factory);
|
{
|
||||||
|
CountOfRequestsToDB++;
|
||||||
if (cache.Data is Dictionary<TKey, T> dictionary)
|
return query.ToDictionary(keySelector);
|
||||||
|
}
|
||||||
|
var cache = GetOrAddCache(tag, factory, obsolescence);
|
||||||
|
var data = cache.Data;
|
||||||
|
if (data is Dictionary<TKey, T> dictionary)
|
||||||
return dictionary.GetValueOrDefault(key);
|
return dictionary.GetValueOrDefault(key);
|
||||||
else
|
else if (data is IEnumerable<T> enumerable)
|
||||||
{
|
{
|
||||||
System.Diagnostics.Trace.TraceWarning($"Use keyed method on keyless cache. Tag: {tag}, type: {typeof(T).Name};");
|
System.Diagnostics.Trace.TraceWarning($"Use keyed method on keyless cache. Tag: {tag}, type: {typeof(T).Name};");
|
||||||
return ((IEnumerable<T>)cache.Data).FirstOrDefault(v => keySelector(v).Equals(key));
|
return enumerable.FirstOrDefault(v => keySelector(v).Equals(key));
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
throw new NotSupportedException("cache.Data has wrong type.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Асинхронно получить запись из кеша по ключу.
|
||||||
|
/// При отсутствии кеша создаст его для всех записей из query.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TKey">тип ключа</typeparam>
|
||||||
|
/// <typeparam name="T">тип значения</typeparam>
|
||||||
|
/// <param name="query"></param>
|
||||||
|
/// <param name="tag">Метка кеша</param>
|
||||||
|
/// <param name="obsolescence">Период устаревания данных</param>
|
||||||
|
/// <param name="keySelector">Делегат получения ключа из записи</param>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="token"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="NotSupportedException">if cache contains trash</exception>
|
||||||
|
public static async Task<T?> FromCacheGetValueOrDefaultAsync<TKey, T>(this IQueryable<T> query, string tag, TimeSpan obsolescence, Func<T, TKey> keySelector, TKey key, CancellationToken token = default)
|
||||||
|
where TKey : notnull
|
||||||
|
{
|
||||||
|
async Task<IEnumerable> factory(CancellationToken token)
|
||||||
|
{
|
||||||
|
CountOfRequestsToDB++;
|
||||||
|
return await query.ToDictionaryAsync(keySelector, token);
|
||||||
|
}
|
||||||
|
var cache = await GetOrAddCacheAsync(tag, factory, obsolescence, token);
|
||||||
|
|
||||||
|
var data = cache.Data;
|
||||||
|
if (data is Dictionary<TKey, T> dictionary)
|
||||||
|
return dictionary.GetValueOrDefault(key);
|
||||||
|
else if (data is IEnumerable<T> enumerable)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Trace.TraceWarning($"Use keyed method on keyless cache. Tag: {tag}, type: {typeof(T).Name};");
|
||||||
|
return enumerable.FirstOrDefault(v => keySelector(v).Equals(key));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw new NotSupportedException("cache.Data has wrong type.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void DropCache<T>(this IQueryable<T> query, string tag)
|
public static void DropCache<T>(this IQueryable<T> query, string tag)
|
||||||
|
@ -14,8 +14,8 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
|
||||||
<PackageReference Include="Google.Apis.Drive.v3" Version="1.55.0.2502" />
|
<PackageReference Include="Google.Apis.Drive.v3" Version="1.57.0.2684" />
|
||||||
<PackageReference Include="Mapster" Version="7.2.0" />
|
<PackageReference Include="Mapster" Version="7.3.0" />
|
||||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using AsbCloudApp.Data;
|
using AsbCloudApp.Data;
|
||||||
using AsbCloudDb.Model;
|
using AsbCloudDb.Model;
|
||||||
using AsbCloudInfrastructure.Services.Cache;
|
using AsbCloudInfrastructure.Services.Cache;
|
||||||
//using AsbCloudInfrastructure.Services.Cache;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -14,40 +13,18 @@ namespace ConsoleApp1
|
|||||||
|
|
||||||
class Program
|
class Program
|
||||||
{
|
{
|
||||||
|
// use ServiceFactory to make services
|
||||||
static void Main(/*string[] args*/)
|
static void Main(/*string[] args*/)
|
||||||
{
|
{
|
||||||
TypeAdapterConfig.GlobalSettings.Default.Config
|
|
||||||
.ForType<DateTimeOffset, DateTime>()
|
|
||||||
.MapWith((source) => source.DateTime);
|
|
||||||
|
|
||||||
TypeAdapterConfig.GlobalSettings.Default.Config
|
|
||||||
.ForType<DateTime, DateTimeOffset>()
|
|
||||||
.MapWith((source) => source == default ? new DateTime(0, DateTimeKind.Utc) : source);
|
|
||||||
|
|
||||||
TypeAdapterConfig.GlobalSettings.Default.Config
|
|
||||||
.ForType<TimeDto, TimeOnly>()
|
|
||||||
.MapWith((source) => source == default? default: source.MakeTimeOnly());
|
|
||||||
|
|
||||||
TypeAdapterConfig.GlobalSettings.Default.Config
|
|
||||||
.ForType<TimeOnly, TimeDto>()
|
|
||||||
.MapWith((source) => new (source));
|
|
||||||
|
|
||||||
var sh = new ScheduleDto{
|
|
||||||
ShiftStart = new TimeDto { Hour = 11, Minute = 30, }
|
|
||||||
};
|
|
||||||
var en = sh.Adapt<Schedule>();
|
|
||||||
var aa = en.Adapt<ScheduleDto>();
|
|
||||||
// use ServiceFactory to make services
|
|
||||||
Console.WriteLine("hit keyboard to start");
|
Console.WriteLine("hit keyboard to start");
|
||||||
Console.ReadLine();
|
Console.ReadLine();
|
||||||
|
|
||||||
for (int i = 0; i < 24; i++)
|
for (int i = 0; i < 24; i++)
|
||||||
{
|
{
|
||||||
//Thread.Sleep(3000);
|
|
||||||
var t = new Thread(_ => {
|
var t = new Thread(_ => {
|
||||||
for (int j = 0; j < 64; j++)
|
for (int j = 0; j < 64; j++)
|
||||||
//Task.Run(GetClastersAsync).Wait();
|
Task.Run(GetClastersAsync).Wait();
|
||||||
GetClasters();
|
//GetClasters();
|
||||||
});
|
});
|
||||||
t.Start();
|
t.Start();
|
||||||
}
|
}
|
||||||
@ -65,14 +42,14 @@ namespace ConsoleApp1
|
|||||||
.Where(t => t.IdTelemetry == 135)
|
.Where(t => t.IdTelemetry == 135)
|
||||||
.OrderBy(t => t.DateTime)
|
.OrderBy(t => t.DateTime)
|
||||||
.Take(100_000)
|
.Take(100_000)
|
||||||
.FromCache("tds", obso, r=>(r.IdTelemetry, r.DateTime))
|
.FromCache("tds", obso)
|
||||||
.ToList();
|
.ToList();
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
Console.WriteLine($"{DateTime.Now}\tth: {Thread.CurrentThread.ManagedThreadId}\trequests {EfCacheL2.RequestsToDb}\ttime {sw.ElapsedMilliseconds}\tcount {cs.Count}");
|
//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}");
|
Console.WriteLine($"{DateTime.Now}\tth: {Thread.CurrentThread.ManagedThreadId}\ttime {sw.ElapsedMilliseconds}\tcount {cs.Count}");
|
||||||
|
|
||||||
GC.Collect();
|
GC.Collect();
|
||||||
Thread.Sleep(100);
|
Thread.Sleep(10);
|
||||||
return (cs.Count, sw.ElapsedMilliseconds);
|
return (cs.Count, sw.ElapsedMilliseconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,11 +64,11 @@ namespace ConsoleApp1
|
|||||||
.FromCacheAsync("tds", obso, r => (r.IdTelemetry, r.DateTime)))
|
.FromCacheAsync("tds", obso, r => (r.IdTelemetry, r.DateTime)))
|
||||||
.ToList();
|
.ToList();
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
Console.WriteLine($"{DateTime.Now}\tth: {Thread.CurrentThread.ManagedThreadId}\trequests {EfCacheL2.RequestsToDb}\ttime {sw.ElapsedMilliseconds}\tcount {cs.Count}");
|
//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}");
|
Console.WriteLine($"{DateTime.Now}\tth: {Thread.CurrentThread.ManagedThreadId}\ttime {sw.ElapsedMilliseconds}\tcount {cs.Count}");
|
||||||
|
|
||||||
GC.Collect();
|
GC.Collect();
|
||||||
Thread.Sleep(100);
|
Thread.Sleep(10);
|
||||||
return (cs.Count, sw.ElapsedMilliseconds);
|
return (cs.Count, sw.ElapsedMilliseconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user