My goal is to put in cache my data. I used AspBoilerPlate as an inspiration for my code.
My WebApi use Asp.net Core.
GitHub
I created a branch on my GitHub for more details:
CacheRepository
public class CacheRepository<TEntity, TPrimaryKey> : Repository<TEntity, TPrimaryKey>, ICacheRepository<TEntity, TPrimaryKey>
where TEntity : class, IEntity<TPrimaryKey>
{
private readonly IMemoryCache _memoryCache;
public CacheRepository(DbContext dbContext, IMemoryCache memoryCache)
: base(dbContext)
{
_memoryCache = memoryCache;
}
public override TEntity FirstOrDefault(TPrimaryKey id)
{
if (!_memoryCache.TryGetValue(GetCacheKey(id), out TEntity result))
{
result = base.FirstOrDefault(id);
if (result != null)
{
PutInCache(result);
}
}
return result;
}
public override void Delete(Expression<Func<TEntity, bool>> predicate)
{
foreach (var entity in GetAll().Where(predicate).ToList())
{
Delete(entity);
}
}
public override void Delete(TEntity entity)
{
base.Delete(entity);
RemoveFromCache(entity.Id);
}
public override void Delete(TPrimaryKey id)
{
base.Delete(id);
RemoveFromCache(id);
}
public override Task DeleteAsync(Expression<Func<TEntity, bool>> predicate)
{
return Task.Run(() => Delete(predicate));
}
public override Task DeleteAsync(TEntity entity)
{
return Task.Run(() => Delete(entity));
}
public override Task DeleteAsync(TPrimaryKey id)
{
return Task.Run(() => Delete(id));
}
public override Task<TEntity> FirstOrDefaultAsync(TPrimaryKey id)
{
return _memoryCache.GetOrCreateAsync(
GetCacheKey(id),
e => base.FirstOrDefaultAsync(id)
);
}
public override List<TEntity> GetAllList()
{
var entities = GetAllEntitiesFromCache();
if (entities == null)
{
entities = base.GetAllList();
foreach (var entity in entities)
{
PutInCache(entity);
}
}
return entities.ToList();
}
public override async Task<List<TEntity>> GetAllListAsync()
{
var entities = GetAllEntitiesFromCache();
if (entities == null)
{
entities = await base.GetAllListAsync();
foreach (var entity in entities)
{
PutInCache(entity);
}
}
return entities.ToList();
}
public override TEntity Get(TPrimaryKey id)
{
return _memoryCache.GetOrCreate(
GetCacheKey(id),
e => base.Get(id)
);
}
public override Task<TEntity> GetAsync(TPrimaryKey id)
{
return _memoryCache.GetOrCreate(
GetCacheKey(id),
e => base.GetAsync(id)
);
}
public override TEntity Insert(TEntity entity)
{
return PutInCache(base.Insert(entity));
}
public override TPrimaryKey InsertAndGetId(TEntity entity)
{
return Insert(entity).Id;
}
public override Task<TPrimaryKey> InsertAndGetIdAsync(TEntity entity)
{
return Task.FromResult(InsertAndGetId(entity));
}
public override Task<TEntity> InsertAsync(TEntity entity)
{
return Task.FromResult(Insert(entity));
}
public override TEntity Update(TEntity entity)
{
return UpdateInCache(base.Update(entity));
}
public override TEntity Update(TPrimaryKey id, Action<TEntity> updateAction)
{
return UpdateInCache(base.Update(id, updateAction));
}
public override Task<TEntity> UpdateAsync(TEntity entity)
{
return Task.FromResult(Update(entity));
}
public override async Task<TEntity> UpdateAsync(TPrimaryKey id, Func<TEntity, Task> updateAction)
{
return UpdateInCache(await base.UpdateAsync(id, updateAction));
}
#region Private
private TEntity PutInCache(TEntity entity)
{
return _memoryCache.Set(GetCacheKey(entity.Id), entity);
}
private void RemoveFromCache(TPrimaryKey id)
{
_memoryCache.Remove(GetCacheKey(id));
}
private TEntity UpdateInCache(TEntity entity)
{
RemoveFromCache(entity.Id);
return PutInCache(entity);
}
private string GetCacheKey(TPrimaryKey id)
{
return typeof(TEntity).FullName + id;
}
private IList<TEntity> GetAllEntitiesFromCache()
{
return _memoryCache.Get<List<TEntity>>(typeof(TEntity));
}
#endregion
}
My questions:
I have so many questions to ask about my CacheRepository, but I had to reduce it to be able to have my post published.
=> Is it a good thing to directly add/edit/delete in CacheRepository on Insert/Edit/Delete before the UnitOfWork.SubmitChanges?
If you have recommendation or suggestion about CacheRepository, please share it, I really want to learn more about that.
Generally removing something from a cache when the process hasn't finished, can cause troubles. It is not yet sure that the changes will take effect bc your dbms will make that decision for you even when using an orm. You will end up digging into all that exceptions and readd stuff while you could have waited for the process to finish before removing the objects from a given cache.
Other things that came to my mind when I digged a little in your code (which is very clean, +1 for that) are the following
context.Set<TEnitity>()... plus wrapping everything inside a repository.Abstractions are good things but sometimes they can also be very bad.
I know the struggle which is why I read a lot of things about these problems and I came across a good solution. CQRS, a mediator and DDD-like services.
I would also recommend to take a look at AutoFac for IoC and DI. Lots of things you are doing manually now (like the factory to create types), can be done automatically using its assembly scanning. It can even be integrated with .NET Core native IoC.
Hope it helps you to get some ideas.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With