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, и я не могу прозрачно использовать провайдера в памяти.

0 ответов

Другие вопросы по тегам