.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>
[AttributeUsage(AttributeTargets.Method, Inherited = true)]
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[0] as MemberExpression).Expression as ConstantExpression;
		var Argument2 = expression.Arguments[1] 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[i].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[0];
		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[0] 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里注册缓存

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数据库连接字符串
},

这里我配置了环境变量,保证我的数据库真实连接不暴露,你也可以改为自己的数据库连接:

图片[1]-.net core 中的MemoryCache的详细使用-牛翰网
配置完后需要先关闭vs,然后再打开才能生效

7.如何使用缓存拦截

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

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

再次调用GetUserCache方法,获取缓存中的id=1的用户数据,可以查看输出结果:

8.启动项目,分别调用方法:GetUserInfo和GetUserCache

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

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

通过以上的讲解:你是否已经理解了MemoryCache的使用呢?

欢迎点赞,关注,评论!

来源链接:https://www.cnblogs.com/chenshibao/p/18854479

© 版权声明
THE END
支持一下吧
点赞15 分享
评论 抢沙发
头像
请文明发言!
提交
头像

昵称

取消
昵称表情代码快捷回复

    暂无评论内容