EntityFramework Ядро данных кэша в памяти с асинхронными операциями
У меня есть рабочее приложение, использующее EF Core 3.0 (превью 8 прямо сейчас). Есть один набор данных, который изменяется очень редко, но запрашивается очень часто. Сервисы, которые фактически выполняют операции с данными, изолированы через очень простой интерфейс.
interface IDataProvider
{
ValueTask<IQueryable<DataRow>> GetDataAsync();
}
До сих пор существовала только одна реализация поставщика данных, основанная на реальной базе данных SQL. Так что, если у нас есть DbContext с DbSet<DataRow> Data
:
class LiveDataProvider : IDataProvider
{
private readonly DataDbContext _dbContext;
public LiveDataProvider(DataDbContext dbContext)
{
_dbContext = dbContext;
}
public ValueTask<IQueryable<DataRow>> GetDataAsync() =>
new ValueTask<IQueryable<DataRow>>(_dbContext.Data.AsNoTracking());
}
Теперь мне поручено добавить кеширование в это приложение. Так что идея состоит в том, чтобы хранить весь Data
установить в оперативной памяти, потому что получение это довольно дорогой запрос к БД, но объем данных составляет всего несколько сотен МБ. Поскольку обновления происходят нечасто, подавляющее большинство запросов попадет в кеш. С этой идеей реализация казалась довольно простой:
class InMemoryDataCache : IDataProvider
{
private readonly IPicksDbContext _picksDbContext;
private readonly IMemoryCache _memoryCache;
public async ValueTask<IQueryable<DataRow>> GetDataAsync()
{
var entryKey = GetEntryKey(); // Implementation is irrelevant.
if (!_memoryCache.TryGetValue(entryKey, out IQueryable<DataRow> data))
{
data = (await _dbContext.Data.AsNoTracking().ToListAsync()).AsQueryable();
_memoryCache.Set(entryKey, data);
}
return data;
}
InMemoryDataCache(DataDbContext dbContext, IMemoryCache memoryCache)
{
_dbContext = dbContext;
_memoryCache = memoryCache;
}
}
Звучит хорошо, не работает - реализации службы используют асинхронные операции на IQueryable
так ToListAsync()
, AnyAsync()
и т. д. Вызов любого из них в кэше List
приведет к EF Core бросать InvalidOperationException
с сообщением Only sources that implement IAsyncEnumerable can be used for Entity Framework asynchronous operations.
Эта ошибка довольно знакома, поскольку юнит-тестирование сервисов показало то же самое. Там я издевался IAsyncQueryProvider
реализация (кричать в MockQueryable), так что здесь я мог бы то же самое.
Кроме того, я мог бы написать свои собственные методы расширения для ToListAsync
, AnyAsync
и т. д. с реализацией
public static Task<List<T>> ToListAsync<T>(this IQueryable<T> queryable)
{
if(queryable is IAsyncEnumerable<T>)
{
return Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync(queryable);
}
return Task.FromResult(queryable.ToList());
}
У обоих много работы и тестов для чего-то настолько тупо простого. Есть ли более надежное решение? Или я загнал себя в угол, привязав свои сервисы к EntityFrameworkQueryableExtensions, и я не могу прозрачно использовать провайдера в памяти.