Generic BLL and DAL for Reference Data with caching and backup files

BLL

internal class Blc<TEntity> : IBlc<TEntity>
{
    private readonly IDao<TEntity> _dao;
 
    public Blc(IDao<TEntity> dao)
    {
        _dao = dao;
    }
 
    /// <summary>
    /// <inheritdoc cref="IBlc{TEntity}.GetAsync()"/>
    /// </summary>
    public async Task<IEnumerable<TEntity>> GetAsync()
    {
        var cache = _dao.GetFromCache();
 
        if (cache != null && cache.Any())
        {
            return cache;
        }
 
        return await _dao.GetAsync();
    }
 
    /// <summary>
    /// <inheritdoc cref="IBlc{TEntity}.GetAsync(Expression{Func{TEntity, bool}})"/>
    /// </summary>
    public async Task<IEnumerable<TEntity>> GetAsync(Expression<Func<TEntity, bool>> expression)
    {
        var cache = _dao.GetFromCache();
 
        if (cache != null && cache.Any())
        {
            return cache.Where(expression);
        }
 
        return await _dao.GetAsync(expression);
    }
 
    /// <summary>
    /// <inheritdoc cref="IBlc{TEntity}.InitializeAsync()"/>
    /// </summary>
    public async Task InitializeAsync()
    {
        var results = await _dao.GetAsync();
 
        if (!results.Any())
        {
            var fileData = _dao.GetFromFile();
 
            if (fileData == null || !fileData.Any())
            {
                throw new KeyNotFoundException("Unable to get refdata from MongoDb nor from its backup file");
            }
 
            await _dao.InsertManyAsync(fileData);
        }
 
        _dao.InsertCache(results.ToList());
    }
}
 
public interface IBlc<TEntity>
{
    /// <summary>
    /// Gets a list of TEntity from MongoDb
    /// </summary>
    Task<IEnumerable<TEntity>> GetAsync();
 
    /// <summary>
    /// Gets a filtered list of TEntity
    /// </summary>
    /// <param name="expression">filter expression</param>
    /// <example>await _blc.GetAsync(x => x.Id == 1);</example>
    Task<IEnumerable<TEntity>> GetAsync(Expression<Func<TEntity, bool>> expression);
 
    /// <summary>
    /// Called during application StartUp.
    /// Checks if data already exists in database.
    /// If not, pulls backup data from a json file and inserts that into database,
    /// then inserts the data into the cache.
    /// </summary>
    /// <exception cref="KeyNotFoundException"></exception>
    Task InitializeAsync();
}

DAL

/// <summary>
/// Ideally, this class will not be called directly, but instead via Blc
/// </summary>
internal class Dao<TEntity> : IDao<TEntity> where TEntity : class
{
    private readonly IMemoryCache _cache;
    private readonly string _cacheKey;
    private readonly string _backupFile;
    private readonly IMongoCollection<TEntity> _mongoCollection;
    private readonly IJsonFileReader _fileReader;
 
    public Dao(
        MongoClient client,
        IOptionsMonitor<Dictionary<string, MongoCollectionConfiguration>> collectionConfigs,
        IOptionsMonitor<AppSettings> appSettings,
        IMemoryCache cache,
        IJsonFileReader fileReader)
    {
        BsonClassMap.RegisterClassMap<TEntity>(cm =>
        {
            cm.AutoMap();
            cm.SetIgnoreExtraElementsIsInherited(true);
            cm.SetIgnoreExtraElements(true);
        });
 
        var database = client.GetDatabase(appSettings.CurrentValue.DbName);
        var collectionConfig = collectionConfigs.CurrentValue.FirstOrDefault(x =>
            x.Key.ToLowerInvariant() == typeof(TEntity).Name.ToLowerInvariant()).Value;
 
        _cacheKey = collectionConfig.CacheKey;
        _backupFile = collectionConfig.BackupFileName;
        _mongoCollection = database.GetCollection<TEntity>(collectionConfig.CollectionName);
        _cache = cache;
        _fileReader = fileReader;
    }
 
    /// <summary>
    /// <inheritdoc cref="IDao{TEntity}.GetAsync()"/>
    /// </summary>
    public async Task<IEnumerable<TEntity>> GetAsync()
    {
        try
        {
            var cursorResult = await _mongoCollection.FindAsync(_ => true);
            return await cursorResult.ToListAsync();
        }
        catch (MongoException ex)
        {
            throw new DatabaseException(ex.ToString());
        }
    }
 
    /// <summary>
    /// <inheritdoc cref="IDao{TEntity}.GetAsync(Expression{Func{TEntity, bool}})"/>
    /// </summary>
    public async Task<IEnumerable<TEntity>> GetAsync(Expression<Func<TEntity, bool>> expression)
    {
        try
        {
            var filter = Builders<TEntity>.Filter.Where(expression);
            var cursorResult = await _mongoCollection.FindAsync(filter);
            return await cursorResult.ToListAsync();
        }
        catch (MongoException ex)
        {
            throw new DatabaseException(ex.ToString());
        }
    }
 
    /// <summary>
    /// <inheritdoc cref="IDao{TEntity}.InsertManyAsync(List{TEntity})"/>
    /// </summary>
    public async Task InsertManyAsync(List<TEntity> data)
    {
        try
        {
            await _mongoCollection.InsertManyAsync(data);
        }
        catch (MongoException ex)
        {
            throw new DatabaseInsertException(ex.ToString());
        }
    }
 
    /// <summary>
    /// <inheritdoc cref="IDao{TEntity}.GetFromFile"/>
    /// </summary>
    public List<TEntity> GetFromFile()
    {
        var json = _fileReader.ReadAllText(_backupFile);
        return JsonSerializer.Deserialize<List<TEntity>>(json);
    }
 
    /// <summary>
    /// <inheritdoc cref="IDao{TEntity}.GetFromCache"/>
    /// </summary>
    public IQueryable<TEntity> GetFromCache()
    {
        _cache.TryGetValue(_cacheKey, out IQueryable<TEntity> cacheResults);
        return cacheResults;
    }
 
    /// <summary>
    /// <inheritdoc cref="IDao{TEntity}.InsertCache(List{TEntity})"/>
    /// </summary>
    public void InsertCache(List<TEntity> data)
    {
        var cacheEntryOptions = new MemoryCacheEntryOptions()
            .SetSlidingExpiration(TimeSpan.FromHours(12));
 
        _cache.Set(_cacheKey, data, cacheEntryOptions);
    }
}
 
public interface IDao<TEntity>
{
    /// <summary>
    /// Pulls a list of TEntity from MongoDb.
    /// </summary>
    Task<IEnumerable<TEntity>> GetAsync();
 
    /// <summary>
    /// Pulls a filtered list of TEntity from MongoDb.
    /// Converts the given Linq expression to a Mongo filter.
    /// </summary>
    Task<IEnumerable<TEntity>> GetAsync(Expression<Func<TEntity, bool>> expression);
 
    /// <summary>
    /// Inserts many TEntity into MongoDb.
    /// </summary>
    /// <param name="data">the list to be inserted into MongoDb</param>
    Task InsertManyAsync(List<TEntity> data);
 
    /// <summary>
    /// Pulls a list of TEntity from its assigned backup json file.
    /// </summary>
    List<TEntity> GetFromFile();
 
    /// <summary>
    /// Pulls a list of TEntity from cache.
    /// </summary>
    IQueryable<TEntity> GetFromCache();
 
    /// <summary>
    /// Inserts a list of TEntity into cache
    /// </summary>
    void InsertCache(List<TEntity> data);
}