How to search redis key in the structure using OOP,Solid,EF code-first,Cross Cutting Concerns with AOP approaach in .net core?
I will give an example that explain the situation. That example may be messy. Let’s think about Microsoft’s Northwind database example. I have Products table. It has columns like ProductName, SupplierId, CategoryId, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, Reorder Level, Discontinued. I want to keep it in redis like PRODUCT~SUPPLIERID~CATEGORYID~PRODUCTNAME
as key and other columns data as value in the same pattern like 'QuantityPerUnit~UnitPrice....'
. And let’s say at .net core side, I have two methods to get a product like GetProductByCategoryId(int categoryId,string productName)
and GetProductBySupplierId(int supplierId,string procductName)
to get a product . In redis, I will search them like KEYS~*~1~Northwoods Cranberry Sauce
to get by categoryId, and KEYS~1~*~Northwoods Cranberry Sauce
to get by supplierId. In my structure, I will cache a product twice becuase of the code I have,but I don’t want to make it happen. How can I store just one product and search them ?
I got codes from udemy lecture.
This is my interface:
using System; using System.Collections.Generic; using System.Text; namespace Core.CrossCuttingConcerns.Caching { public interface ICacheManager { T Get<T>(string key); object Get(string key); void Add(string key, object data, int duration); bool IsAdd(string key); void Remove(string key); void RemoveByPattern(string pattern); } }
This is ImemoryCache
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; using Core.Utilities.IoC; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; namespace Core.CrossCuttingConcerns.Caching.Microsoft { public class MemoryCacheManager : ICacheManager { private IMemoryCache _cache; public MemoryCacheManager() { _cache = ServiceTool.ServiceProvider.GetService<IMemoryCache>(); } public T Get<T>(string key) { return _cache.Get<T>(key); } public object Get(string key) { return _cache.Get(key); } public void Add(string key, object data, int duration) { _cache.Set(key, data, TimeSpan.FromMinutes(duration)); } public bool IsAdd(string key) { return _cache.TryGetValue(key, out _); } public void Remove(string key) { _cache.Remove(key); } public void RemoveByPattern(string pattern) { var cacheEntriesCollectionDefinition = typeof(MemoryCache).GetProperty("EntriesCollection", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); var cacheEntriesCollection = cacheEntriesCollectionDefinition.GetValue(_cache) as dynamic; List<ICacheEntry> cacheCollectionValues = new List<ICacheEntry>(); foreach (var cacheItem in cacheEntriesCollection) { ICacheEntry cacheItemValue = cacheItem.GetType().GetProperty("Value").GetValue(cacheItem, null); cacheCollectionValues.Add(cacheItemValue); } var regex = new Regex(pattern, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase); var keysToRemove = cacheCollectionValues.Where(d => regex.IsMatch(d.Key.ToString())).Select(d => d.Key).ToList(); foreach (var key in keysToRemove) { _cache.Remove(key); } } } }
This is IDisributedCache that has only GetString and SetString no method for searching. I didn’t make it work as well.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; using Core.Utilities.IoC; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.DependencyInjection; namespace Core.CrossCuttingConcerns.Caching.Redis { public class DistributedCacheManager : ICacheManager { private readonly IDistributedCache _cache; public DistributedCacheManager() { _cache = ServiceTool.ServiceProvider.GetService<IDistributedCache>(); } public T Get<T>(string key) { return _cache.Get<T>(key); } public object Get(string key) { return _cache.Get(key); } public void Add(string key, object data, int duration) { _cache.Set(key, data, TimeSpan.FromMinutes(duration)); } public bool IsAdd(string key) { return _cache.(key, out _); } public void Remove(string key) { _cache.Remove(key); } public void RemoveByPattern(string pattern) { var cacheEntriesCollectionDefinition = typeof(MemoryCache).GetProperty("EntriesCollection", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); var cacheEntriesCollection = cacheEntriesCollectionDefinition.GetValue(_cache) as dynamic; List<ICacheEntry> cacheCollectionValues = new List<ICacheEntry>(); foreach (var cacheItem in cacheEntriesCollection) { ICacheEntry cacheItemValue = cacheItem.GetType().GetProperty("Value").GetValue(cacheItem, null); cacheCollectionValues.Add(cacheItemValue); } var regex = new Regex(pattern, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase); var keysToRemove = cacheCollectionValues.Where(d => regex.IsMatch(d.Key.ToString())).Select(d => d.Key).ToList(); foreach (var key in keysToRemove) { _cache.Remove(key); } } } }
This is Module where i configure.
using System; using System.Collections.Generic; using System.Diagnostics; using System.Text; using Core.CrossCuttingConcerns.Caching; using Core.CrossCuttingConcerns.Caching.Redis; //using Core.CrossCuttingConcerns.Caching.Microsoft; using Core.Utilities.IoC; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; namespace Core.DependencyResolvers { public class CoreModule : ICoreModule { public void Load(IServiceCollection services) { services.AddMemoryCache(); services.AddStackExchangeRedisCache(option => { option.Configuration ="localhost:6379" option.InstanceName = "master"; }); services.AddSingleton<ICacheManager, DistributedCacheManager>(); services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); services.AddSingleton<Stopwatch>(); } } }
This is add cache aspect
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Castle.DynamicProxy; using Core.CrossCuttingConcerns.Caching; using Core.Utilities.Interceptors; using Core.Utilities.IoC; using Microsoft.Extensions.DependencyInjection; namespace Core.Aspects.Autofac.Caching { public class CacheAspect : MethodInterception { private int _duration; private ICacheManager _cacheManager; public CacheAspect(int duration = 60) { _duration = duration; _cacheManager = ServiceTool.ServiceProvider.GetService<ICacheManager>(); } public override void Intercept(IInvocation invocation) { var methodName = string.Format($"{invocation.Method.ReflectedType.FullName}.{invocation.Method.Name}"); var arguments = invocation.Arguments.ToList(); var key = $"{methodName}({string.Join(",", arguments.Select(x => x?.ToString() ?? "<Null>"))})"; if (_cacheManager.IsAdd(key)) { invocation.ReturnValue = _cacheManager.Get(key); return; } invocation.Proceed(); _cacheManager.Add(key, invocation.ReturnValue, _duration); } } }
This is remove cache aspect
using System; using System.Collections.Generic; using System.Text; using Castle.DynamicProxy; using Core.CrossCuttingConcerns.Caching; using Core.Utilities.Interceptors; using Core.Utilities.IoC; using Microsoft.Extensions.DependencyInjection; namespace Core.Aspects.Autofac.Caching { public class CacheRemoveAspect : MethodInterception { private string _pattern; private ICacheManager _cacheManager; public CacheRemoveAspect(string pattern) { _pattern = pattern; _cacheManager = ServiceTool.ServiceProvider.GetService<ICacheManager>(); } protected override void OnSuccess(IInvocation invocation) { _cacheManager.RemoveByPattern(_pattern); } } }
I want to make code clean like this for redis caching I want to use like this, but it caches by getting this method’s name and its parameters as key to cache it. if I do that I will cache a product twice. but I want to make it once and search in them.
[CacheAspect(duration: 10)] public IDataResult<List<Product>> GetListByCategory(int categoryId) { return new SuccessDataResult<List<Product>>(_productDal.GetList(p => p.CategoryId == categoryId).ToList()); }