生儒夏花 發表於 2025-4-29 23:46:00

.net core 中的MemoryCache的详细使用

<h1 id="项目搭建了一个基础的框架实现缓存的aop拦截首次查询从数据库获取再写入缓存设置过期时间再次查询数据时从缓存获取">项目搭建了一个基础的框架,实现缓存的AOP拦截,首次查询从数据库获取,再写入缓存,设置过期时间,再次查询数据时从缓存获取。</h1>
<h2 id="话不多说我们来上代码实现">话不多说我们来上代码实现:</h2>
<h2 id="1定义缓存的接口和实现类">1.定义缓存的接口和实现类</h2>
<p><img src="https://img2024.cnblogs.com/blog/2212230/202504/2212230-20250429223214923-58981132.png"></p>
<p>定义缓存接口ICachingProvider和实现类MemoryCaching:</p>
<pre><code>        /// &lt;summary&gt;
        /// 简单的缓存接口,只有查询和添加,以后会进行扩展
        /// &lt;/summary&gt;
        public interface ICachingProvider
        {
                object Get(string cacheKey);

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

/// &lt;summary&gt;
/// 实例化缓存接口ICaching
/// &lt;/summary&gt;
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));
       }
}
</code></pre>
<h2 id="2定义缓存的特性cachingattribute">2.定义缓存的特性CachingAttribute:</h2>
<pre><code>/// &lt;summary&gt;
/// 这个Attribute就是使用时候的验证,把它添加到要缓存数据的方法中,即可完成缓存的操作。
/// &lt;/summary&gt;

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

}
</code></pre>
<h2 id="3定义memorycachesetup-启动服务注册">3.定义MemoryCacheSetup 启动服务注册</h2>
<pre><code>/// &lt;summary&gt;
/// Memory缓存 启动服务
/// &lt;/summary&gt;
public static class MemoryCacheSetup
{
        public static void AddMemoryCacheSetup(this IServiceCollection services)
        {
                if (services == null) throw new ArgumentNullException(nameof(services));

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

        }
}
</code></pre>
<h2 id="4cacheaopbase缓存aop基础类面向切换的内存缓存使用">4.CacheAopBase缓存aop基础类,面向切换的内存缓存使用</h2>
<p>定义缓存aop基类:</p>
<pre><code>public abstract class CacheAopBase : IInterceptor
{
        /// &lt;summary&gt;
        /// AOP的拦截方法
        /// &lt;/summary&gt;
        /// &lt;param name="invocation"&gt;&lt;/param&gt;
        public abstract void Intercept(IInvocation invocation);

        /// &lt;summary&gt;
        /// 自定义缓存的key
        /// &lt;/summary&gt;
        /// &lt;param name="invocation"&gt;&lt;/param&gt;
        /// &lt;returns&gt;&lt;/returns&gt;
        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(':');
        }

        /// &lt;summary&gt;
        /// object 转 string
        /// &lt;/summary&gt;
        /// &lt;param name="arg"&gt;&lt;/param&gt;
        /// &lt;returns&gt;&lt;/returns&gt;
        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 &amp;&amp; binary.Right is ConstantExpression)//解析x=&gt;x.Name=="123" x.Age==123这类
                                return ResolveFunc(binary.Left, binary.Right, binary.NodeType);
                        if (binary.Left is MethodCallExpression &amp;&amp; binary.Right is ConstantExpression)//解析x=&gt;x.Name.Contains("xxx")==false这类的
                        {
                                object value = (binary.Right as ConstantExpression).Value;
                                return ResolveLinqToObject(binary.Left, value, binary.NodeType);
                        }
                        if ((binary.Left is MemberExpression &amp;&amp; binary.Right is MemberExpression)
                                || (binary.Left is MemberExpression &amp;&amp; binary.Right is UnaryExpression))//解析x=&gt;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=&gt;x.Name.Contains("xxx")或!array.Contains(x.Name)这类
                                return ResolveLinqToObject(unary.Operand, false);
                        if (unary.Operand is MemberExpression &amp;&amp; unary.NodeType == ExpressionType.Not)//解析x=&gt;!x.isDeletion这样的
                        {
                                ConstantExpression constant = Expression.Constant(false);
                                return ResolveFunc(unary.Operand, constant, ExpressionType.Equal);
                        }
                }
                if (expression is MemberExpression &amp;&amp; expression.NodeType == ExpressionType.MemberAccess)//解析x=&gt;x.isDeletion这样的
                {
                        MemberExpression member = expression as MemberExpression;
                        ConstantExpression constant = Expression.Constant(true);
                        return ResolveFunc(member, constant, ExpressionType.Equal);
                }
                if (expression is MethodCallExpression)//x=&gt;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 "&lt;&gt;";
                        case ExpressionType.LessThan:
                                return "&lt;";
                        case ExpressionType.LessThanOrEqual:
                                return "&lt;=";
                        case ExpressionType.GreaterThan:
                                return "&gt;";
                        case ExpressionType.GreaterThanOrEqual:
                                return "&gt;=";
                        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&lt;string&gt; SetInPara = new List&lt;string&gt;();
                for (int i = 0; i &lt; 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;
        }

}
</code></pre>
<p>缓存AOP的实现:</p>
<pre><code>/// &lt;summary&gt;
/// 面向切换的内存缓存使用
/// &lt;/summary&gt;
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 =&gt; x.GetType() == typeof(CachingAttribute));
                var CachingAttribute = method.GetCustomAttribute&lt;CachingAttribute&gt;(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();//直接执行被拦截方法
                }
        }

}
</code></pre>
<h2 id="5定义autofacmoduleregister-模块注册类实现模块间的解耦在这个类中注册所有的接口和服务等在这里实现memorycacheaop的拦截开启">5.定义AutofacModuleRegister 模块注册类实现模块间的解耦,在这个类中注册所有的接口和服务等:在这里实现MemoryCacheAop的拦截开启</h2>
<pre><code>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) &amp;&amp; File.Exists(repositoryDllFile)))
                {
                        var msg = "Repository.dll和Service.dll 丢失,因为项目解耦了,所以需要先F6编译,再F5运行,请检查 bin 文件夹,并拷贝。";
                        throw new Exception(msg);
                }

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

        }
}
</code></pre>
<p>在program里注册缓存<br>
<img src="https://img2024.cnblogs.com/blog/2212230/202504/2212230-20250429225244170-360525406.png"></p>
<h2 id="6配置数据库链接">6.配置数据库链接</h2>
<p>在appsettings.json配置文件上配置数据库链接,以及redis缓存连接,本文档暂时先不讲解redis缓存(等待下一篇)</p>
<p>如果设置UseCache=true,那么就开启redis缓存,设置UseCache=false则开启memoryCache缓存</p>
<pre><code>//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数据库连接字符串
},
</code></pre>
<p>这里我配置了环境变量,保证我的数据库真实连接不暴露,你也可以改为自己的数据库连接:</p>
<p><img src="https://img2024.cnblogs.com/blog/2212230/202504/2212230-20250429231259450-497079134.png"></p>
<p><img src="https://img2024.cnblogs.com/blog/2212230/202504/2212230-20250429231353764-979994055.png"><br>
配置完后需要先关闭vs,然后再打开才能生效</p>
<h2 id="7如何使用缓存拦截">7.如何使用缓存拦截</h2>
<p>首先在服务的基类实现中添加Caching缓存特性;<br>
在BaseServices里添加缓存特性,并设置过期时间为:30分钟:Caching(AbsoluteExpiration = 3)<br>
<img src="https://img2024.cnblogs.com/blog/2212230/202504/2212230-20250429225527267-512472188.png"></p>
<p>其次在GetUserInfo方法调用QueryByIdAsync方法查询id=1的用户<br>
<img src="https://img2024.cnblogs.com/blog/2212230/202504/2212230-20250429225747229-724874559.png"></p>
<p>再次调用GetUserCache方法,获取缓存中的id=1的用户数据,可以查看输出结果:</p>
<h2 id="8启动项目分别调用方法getuserinfo和getusercache">8.启动项目,分别调用方法:GetUserInfo和GetUserCache</h2>
<p>调用GetUserInfo查询数据,写入缓存成功<br>
<img src="https://img2024.cnblogs.com/blog/2212230/202504/2212230-20250429234209825-1963651830.png"></p>
<p>再次调用GetUserCache,一样的缓存key和返回一样的用户信息:<br>
<img src="https://img2024.cnblogs.com/blog/2212230/202504/2212230-20250429234417243-1740609428.png"></p>
<p>通过以上的讲解:你是否已经理解了MemoryCache的使用呢?</p>
<p>源代码地址:https://gitee.com/chenshibao/webapi</p>
<p>谢谢阅览,如果对你有帮助欢迎一键三连:点赞,收藏,评论!</p><br><br>
来源:https://www.cnblogs.com/chenshibao/p/18854479
頁: [1]
查看完整版本: .net core 中的MemoryCache的详细使用