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);
}