玛凶 发表于 2025-5-29 10:57:45

.net core 中的MemoryCache的详细使用

项目搭建了一个基础的框架,实现缓存的AOP拦截,首次查询从数据库获取,再写入缓存,设置过期时间,再次查询数据时从缓存获取。

话不多说我们来上代码实现:

1.定义缓存的接口和实现类


定义缓存接口ICachingProvider和实现类MemoryCaching:
        /// <summary>
        /// 简单的缓存接口,只有查询和添加,以后会进行扩展
        /// </summary>
        public interface ICachingProvider
        {
                object Get(string cacheKey);

                void Set(string cacheKey, object cacheValue, int timeSpan);
        }

/// <summary>
/// 实例化缓存接口ICaching
/// </summary>
public class MemoryCaching : ICachingProvider
{
       //引用Microsoft.Extensions.Caching.Memory;这个和.net 还是不一样,没有了Httpruntime了
       private readonly IMemoryCache _cache;
       //还是通过构造函数的方法,获取
       public MemoryCaching(IMemoryCache cache)
       {
               _cache = cache;
       }

       public object Get(string cacheKey)
       {
               return _cache.Get(cacheKey);
       }

       public void Set(string cacheKey, object cacheValue, int timeSpan)
       {
               _cache.Set(cacheKey, cacheValue, TimeSpan.FromSeconds(timeSpan * 60));
       }
}2.定义缓存的特性CachingAttribute:

/// <summary>
/// 这个Attribute就是使用时候的验证,把它添加到要缓存数据的方法中,即可完成缓存的操作。
/// </summary>

public class CachingAttribute : System.Attribute
{
        /// <summary>
        /// 缓存绝对过期时间(分钟)
        /// </summary>
        public int AbsoluteExpiration { get; set; } = 30;

}3.定义MemoryCacheSetup 启动服务注册

/// <summary>
/// Memory缓存 启动服务
/// </summary>
public static class MemoryCacheSetup
{
        public static void AddMemoryCacheSetup(this IServiceCollection services)
        {
                if (services == null) throw new ArgumentNullException(nameof(services));

                services.AddScoped<ICachingProvider, MemoryCaching>();
                services.AddSingleton<IMemoryCache>(factory =>
                {
                        var cache = new MemoryCache(new MemoryCacheOptions());
                        return cache;
                });

        }
}4.CacheAopBase缓存aop基础类,面向切换的内存缓存使用

定义缓存aop基类:
public abstract class CacheAopBase : IInterceptor
{
        /// <summary>
        /// AOP的拦截方法
        /// </summary>
        /// <param name="invocation"></param>
        public abstract void Intercept(IInvocation invocation);

        /// <summary>
        /// 自定义缓存的key
        /// </summary>
        /// <param name="invocation"></param>
        /// <returns></returns>
        protected string CustomCacheKey(IInvocation invocation)
        {
                var typeName = invocation.TargetType.Name;
                var methodName = invocation.Method.Name;
                var methodArguments = invocation.Arguments.Select(GetArgumentValue).Take(3).ToList();//获取参数列表,最多三个

                string key = $"{typeName}:{methodName}:";
                foreach (var param in methodArguments)
                {
                        key = $"{key}{param}:";
                }

                return key.TrimEnd(':');
        }

        /// <summary>
        /// object 转 string
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected static string GetArgumentValue(object arg)
        {
                if (arg is DateTime)
                        return ((DateTime)arg).ToString("yyyyMMddHHmmss");

                if (arg is string || arg is ValueType)
                        return arg.ToString();

                if (arg != null)
                {
                        if (arg is Expression)
                        {
                                var obj = arg as Expression;
                                var result = Resolve(obj);
                                return CommonHelper.Md5For16(result);
                        }
                        else if (arg.GetType().IsClass)
                        {
                                return CommonHelper.Md5For16(Newtonsoft.Json.JsonConvert.SerializeObject(arg));
                        }
                }
                return string.Empty;
        }

        private static string Resolve(Expression expression)
        {
                if (expression is LambdaExpression)
                {
                        LambdaExpression lambda = expression as LambdaExpression;
                        expression = lambda.Body;
                        return Resolve(expression);
                }
                if (expression is BinaryExpression)
                {
                        BinaryExpression binary = expression as BinaryExpression;
                        if (binary.Left is MemberExpression && binary.Right is ConstantExpression)//解析x=>x.Name=="123" x.Age==123这类
                                return ResolveFunc(binary.Left, binary.Right, binary.NodeType);
                        if (binary.Left is MethodCallExpression && binary.Right is ConstantExpression)//解析x=>x.Name.Contains("xxx")==false这类的
                        {
                                object value = (binary.Right as ConstantExpression).Value;
                                return ResolveLinqToObject(binary.Left, value, binary.NodeType);
                        }
                        if ((binary.Left is MemberExpression && binary.Right is MemberExpression)
                                || (binary.Left is MemberExpression && binary.Right is UnaryExpression))//解析x=>x.Date==DateTime.Now这种
                        {
                                LambdaExpression lambda = Expression.Lambda(binary.Right);
                                Delegate fn = lambda.Compile();
                                ConstantExpression value = Expression.Constant(fn.DynamicInvoke(null), binary.Right.Type);
                                return ResolveFunc(binary.Left, value, binary.NodeType);
                        }
                }
                if (expression is UnaryExpression)
                {
                        UnaryExpression unary = expression as UnaryExpression;
                        if (unary.Operand is MethodCallExpression)//解析!x=>x.Name.Contains("xxx")或!array.Contains(x.Name)这类
                                return ResolveLinqToObject(unary.Operand, false);
                        if (unary.Operand is MemberExpression && unary.NodeType == ExpressionType.Not)//解析x=>!x.isDeletion这样的
                        {
                                ConstantExpression constant = Expression.Constant(false);
                                return ResolveFunc(unary.Operand, constant, ExpressionType.Equal);
                        }
                }
                if (expression is MemberExpression && expression.NodeType == ExpressionType.MemberAccess)//解析x=>x.isDeletion这样的
                {
                        MemberExpression member = expression as MemberExpression;
                        ConstantExpression constant = Expression.Constant(true);
                        return ResolveFunc(member, constant, ExpressionType.Equal);
                }
                if (expression is MethodCallExpression)//x=>x.Name.Contains("xxx")或array.Contains(x.Name)这类
                {
                        MethodCallExpression methodcall = expression as MethodCallExpression;
                        return ResolveLinqToObject(methodcall, true);
                }
                var body = expression as BinaryExpression;
                //已经修改过代码body应该不会是null值了
                if (body == null)
                        return string.Empty;
                var Operator = GetOperator(body.NodeType);
                var Left = Resolve(body.Left);
                var Right = Resolve(body.Right);
                string Result = string.Format("({0} {1} {2})", Left, Operator, Right);
                return Result;
        }

        private static string GetOperator(ExpressionType expressiontype)
        {
                switch (expressiontype)
                {
                        case ExpressionType.And:
                                return "and";
                        case ExpressionType.AndAlso:
                                return "and";
                        case ExpressionType.Or:
                                return "or";
                        case ExpressionType.OrElse:
                                return "or";
                        case ExpressionType.Equal:
                                return "=";
                        case ExpressionType.NotEqual:
                                return "<>";
                        case ExpressionType.LessThan:
                                return "<";
                        case ExpressionType.LessThanOrEqual:
                                return "<=";
                        case ExpressionType.GreaterThan:
                                return ">";
                        case ExpressionType.GreaterThanOrEqual:
                                return ">=";
                        default:
                                throw new Exception(string.Format("不支持{0}此种运算符查找!" + expressiontype));
                }
        }

        private static string ResolveFunc(Expression left, Expression right, ExpressionType expressiontype)
        {
                var Name = (left as MemberExpression).Member.Name;
                var Value = (right as ConstantExpression).Value;
                var Operator = GetOperator(expressiontype);
                return Name + Operator + Value ?? "null";
        }

        private static string ResolveLinqToObject(Expression expression, object value, ExpressionType? expressiontype = null)
        {
                var MethodCall = expression as MethodCallExpression;
                var MethodName = MethodCall.Method.Name;
                switch (MethodName)
                {
                        case "Contains":
                                if (MethodCall.Object != null)
                                        return Like(MethodCall);
                                return In(MethodCall, value);
                        case "Count":
                                return Len(MethodCall, value, expressiontype.Value);
                        case "LongCount":
                                return Len(MethodCall, value, expressiontype.Value);
                        default:
                                throw new Exception(string.Format("不支持{0}方法的查找!", MethodName));
                }
        }

        private static string In(MethodCallExpression expression, object isTrue)
        {
                var Argument1 = (expression.Arguments as MemberExpression).Expression as ConstantExpression;
                var Argument2 = expression.Arguments as MemberExpression;
                var Field_Array = Argument1.Value.GetType().GetFields().First();
                object[] Array = Field_Array.GetValue(Argument1.Value) as object[];
                List<string> SetInPara = new List<string>();
                for (int i = 0; i < Array.Length; i++)
                {
                        string Name_para = "InParameter" + i;
                        string Value = Array.ToString();
                        SetInPara.Add(Value);
                }
                string Name = Argument2.Member.Name;
                string Operator = Convert.ToBoolean(isTrue) ? "in" : " not in";
                string CompName = string.Join(",", SetInPara);
                string Result = string.Format("{0} {1} ({2})", Name, Operator, CompName);
                return Result;
        }
        private static string Like(MethodCallExpression expression)
        {

                var Temp = expression.Arguments;
                LambdaExpression lambda = Expression.Lambda(Temp);
                Delegate fn = lambda.Compile();
                var tempValue = Expression.Constant(fn.DynamicInvoke(null), Temp.Type);
                string Value = string.Format("%{0}%", tempValue);
                string Name = (expression.Object as MemberExpression).Member.Name;
                string Result = string.Format("{0} like {1}", Name, Value);
                return Result;
        }


        private static string Len(MethodCallExpression expression, object value, ExpressionType expressiontype)
        {
                object Name = (expression.Arguments as MemberExpression).Member.Name;
                string Operator = GetOperator(expressiontype);
                string Result = string.Format("len({0}){1}{2}", Name, Operator, value.ToString());
                return Result;
        }

}缓存AOP的实现:
/// <summary>
/// 面向切换的内存缓存使用
/// </summary>
public class MemoryCacheAop : CacheAopBase
{
        //通过注入的方式,把缓存操作接口通过构造函数注入
        private readonly ICachingProvider _cache;
        public MemoryCacheAop(ICachingProvider cache)
        {
                _cache = cache;
        }

        //Intercept方法是拦截的关键所在,也是IInterceptor接口中的唯一定义
        public override void Intercept(IInvocation invocation)
        {
                var method = invocation.MethodInvocationTarget ?? invocation.Method;
                //对当前方法的特性验证
                //如果需要验证
                //var CachingAttribute = method.GetCustomAttributes(true).FirstOrDefault(x => x.GetType() == typeof(CachingAttribute));
                var CachingAttribute = method.GetCustomAttribute<CachingAttribute>(true);
                Console.WriteLine(method.Name);
                Console.WriteLine(method.DeclaringType.FullName);

                if (CachingAttribute is CachingAttribute qCachingAttribute)
                {
                        //获取自定义缓存键
                        var cacheKey = CustomCacheKey(invocation);
                        //根据key获取相应的缓存值
                        var cacheValue = _cache.Get(cacheKey);
                        if (cacheValue != null)
                        {
                                //将当前获取到的缓存值,赋值给当前执行方法
                                invocation.ReturnValue = cacheValue;
                                return;
                        }
                        //去执行当前的方法
                        invocation.Proceed();
                        //存入缓存
                        if (!string.IsNullOrWhiteSpace(cacheKey))
                        {
                                _cache.Set(cacheKey, invocation.ReturnValue, qCachingAttribute.AbsoluteExpiration);
                        }
                }
                else
                {
                        invocation.Proceed();//直接执行被拦截方法
                }
        }

}5.定义AutofacModuleRegister 模块注册类实现模块间的解耦,在这个类中注册所有的接口和服务等:在这里实现MemoryCacheAop的拦截开启

public class AutofacModuleRegister : Autofac.Module
{
        protected override void Load(ContainerBuilder builder)
        {
                var basePath = AppContext.BaseDirectory;

                #region 带有接口层的服务注入

                var servicesDllFile = Path.Combine(basePath, "Net.Service.dll");
                var repositoryDllFile = Path.Combine(basePath, "Net.Repository.dll");

                if (!(File.Exists(servicesDllFile) && File.Exists(repositoryDllFile)))
                {
                        var msg = "Repository.dll和Service.dll 丢失,因为项目解耦了,所以需要先F6编译,再F5运行,请检查 bin 文件夹,并拷贝。";
                        throw new Exception(msg);
                }

                // AOP 开关,如果想要打开指定的功能,只需要在 appsettigns.json 对应的配置项设置为 true 就行。
                var cacheType = new List<Type>();
                if (AppSettingsConstVars.RedisUseCache)
                {
                        //builder.RegisterType<RedisCacheAop>();
                        //cacheType.Add(typeof(RedisCacheAop));
                }
                else
                {
                        builder.RegisterType<MemoryCacheAop>();
                        cacheType.Add(typeof(MemoryCacheAop));
                }

                //// 获取 Service.dll 程序集服务,并注册
                //var assemblysServices = Assembly.LoadFrom(servicesDllFile);
                ////支持属性注入依赖重复
                //builder.RegisterAssemblyTypes(assemblysServices).AsImplementedInterfaces().InstancePerDependency()
                //    .PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies);

                //// 获取 Repository.dll 程序集服务,并注册
                //var assemblysRepository = Assembly.LoadFrom(repositoryDllFile);
                ////支持属性注入依赖重复
                //builder.RegisterAssemblyTypes(assemblysRepository).AsImplementedInterfaces().InstancePerDependency()
                //    .PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies);


                // 获取 Service.dll 程序集服务,并注册
                var assemblysServices = Assembly.LoadFrom(servicesDllFile);
                builder.RegisterAssemblyTypes(assemblysServices)
                        .AsImplementedInterfaces()
                        .InstancePerDependency()
                        .PropertiesAutowired()
                        .EnableInterfaceInterceptors()//引用Autofac.Extras.DynamicProxy;
                        .InterceptedBy(cacheType.ToArray());//允许将拦截器服务的列表分配给注册。

                // 获取 Repository.dll 程序集服务,并注册
                var assemblysRepository = Assembly.LoadFrom(repositoryDllFile);
                builder.RegisterAssemblyTypes(assemblysRepository)
                        .AsImplementedInterfaces()
                        .PropertiesAutowired()
                        .InstancePerDependency();


                #endregion

        }
}在program里注册缓存
https://img2024.cnblogs.com/blog/2212230/202504/2212230-20250429225244170-360525406.png
6.配置数据库链接

在appsettings.json配置文件上配置数据库链接,以及redis缓存连接,本文档暂时先不讲解redis缓存(等待下一篇)
如果设置UseCache=true,那么就开启redis缓存,设置UseCache=false则开启memoryCache缓存
//redis为必须启动项,请保持redis为正常可用
"RedisConfig": {
"UseCache": false, //启用redis作为内存选择
// 如果采用容器化部署Service 要写成redis的服务名,否则写地址
"ConnectionString": "127.0.0.1:6379,password=CoreShop,connectTimeout=3000,connectRetry=1,syncTimeout=10000,DefaultDatabase=10" //redis数据库连接字符串
},这里我配置了环境变量,保证我的数据库真实连接不暴露,你也可以改为自己的数据库连接:


配置完后需要先关闭vs,然后再打开才能生效
7.如何使用缓存拦截

首先在服务的基类实现中添加Caching缓存特性;
在BaseServices里添加缓存特性,并设置过期时间为:30分钟:Caching(AbsoluteExpiration = 3)

其次在GetUserInfo方法调用QueryByIdAsync方法查询id=1的用户

再次调用GetUserCache方法,获取缓存中的id=1的用户数据,可以查看输出结果:
8.启动项目,分别调用方法:GetUserInfo和GetUserCache

调用GetUserInfo查询数据,写入缓存成功

再次调用GetUserCache,一样的缓存key和返回一样的用户信息:

通过以上的讲解:你是否已经理解了MemoryCache的使用呢?
源代码地址:https://gitee.com/chenshibao/webapi
谢谢阅览,如果对你有帮助欢迎一键三连:点赞,收藏,评论!

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: .net core 中的MemoryCache的详细使用