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
///
/// Кеширование запросов EF.
/// Кеш не отслеживается ChangeTracker.
///
public static class EfCacheExtensions
{
private static readonly Dictionary 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 : IEnumerable
{
private struct ConvertedData
{
public TEntity? Entity;
public TModel? Model;
}
ConvertedData[] data;
public Func convert { get; }
public YieldConvertedData(TEntity[] entities, Func convert)
{
data = (entities.Select(x => new ConvertedData {
Entity = x,
Model = default }))
.ToArray();
this.convert = convert;
}
class YieldConvertedDataEnumerator : IEnumerator
{
private readonly ConvertedData[] data;
private readonly Func convert;
private int position = -1;
public YieldConvertedDataEnumerator(ConvertedData[] data, Func 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 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 GetData()
{
if (Data is IEnumerable typedData)
return typedData;
throw new TypeAccessException("Cache data has wrong type. Possible 'tag' is not unique.");
}
internal IEnumerable GetData(Func convert, int attempt = 1)
{
if (Data is IEnumerable typedData)
return typedData;
if (Data is IEnumerable typedEntityData)
{
if (semaphore.Wait(0))
{
try
{
var convertedData = new YieldConvertedData(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