本文将讲解如何在ABP中使用Redis Cache以及使用过程中遇到的各种问题。下面就直接讲解使用步骤,Redis环境的搭建请直接网上搜索。

使用步骤:

一、ABP环境搭建

  1. http://www.aspnetboilerplate.com/Templates下载一个ABP项目的模板,项目 类型选择Angularjs+EntityFramework,项目名称为“UsingRedisInAbp”
  2. 生成数据库,并初始化基本数据。在包管理器的控制台上运行Updata-Database命令,运行时需要注意,默认项目要选中“UsingRedisInAbp.EntityFramework”,启动项目要设置为“UsingRedisInAbp.Web”
  3. 在nuget里面添加对”Abp.RedisCache”的引用,我引用的0.7.8.1版本

二、替换默认的缓存管理器

修改UsingRedisInAbpApplicationModule类的代码,主要是修改默认缓存管理器和Redis的连接字符串,修改后的完整代码如下:

  1. [DependsOn(typeof(UsingRedisInAbpCoreModule), typeof(AbpAutoMapperModule))]
  2. public class UsingRedisInAbpApplicationModule : AbpModule
  3. {
  4. public override void PreInitialize()
  5. {
  6. base.PreInitialize();
  7. IocManager.Register<ICacheManager, AbpRedisCacheManager>();
  8. //如果Redis在本机,并且使用的默认端口,下面的代码可以不要
  9. //Configuration.Modules.AbpRedisCacheModule().ConnectionStringKey = "KeyName";
  10. }
  11.  
  12. public override void Initialize()
  13. {
  14. IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
  15. }
  16. }

三、缓存的使用

现在我们编译一下项目,编译通过后我们按F5运行,如果看到如下界面,表示运行成功了

下面我们看看Redis里是否有相关的缓存信息,用Redis Desktop Manager连接Redis,可以看到如下信息,说明信息已经缓存成功了

四、自定义缓存信息的读取与设置

为了演示方便,我们以读取文章信息为例来说明,文章信息只包含一个”Title”字段.

  1. 在.Core项目里添加文章类,然后在UsingRedisInAbpDbContext类里添加一个Articles属性
  2. 使用Add-Migration添加信息,然后更新数据库
  3. 在.Application项目里添加Caching文件夹,并在里面添加读取和设置缓存的通用接口和实现类,实现类CacheService.cs的代码如下:
  1. public class CacheService : ICacheService,ISingletonDependency
  2. {
  3. public ICacheManager CacheManager { get; set; }
  4.  
  5. public TValue GetCachedEntity<TKey, TValue>(TKey key) where TValue : class,IEntity<TKey>
  6. {
  7. var cache = CacheManager.GetCache<TKey, TValue>(typeof(TValue).Name);
  8. var item = cache.Get(key, () =>
  9. {
  10. var repository = IocManager.Instance.Resolve<IRepository<TValue, TKey>>();
  11. var entity = repository.FirstOrDefault(key);
  12. if (entity == null)
  13. {
  14. throw new UserFriendlyException(string.Format("读取的信息不存在,Key:{0}",key));
  15. }
  16. return entity;
  17. });
  18.  
  19. return item;
  20. }
  21.  
  22. public TValue GetCachedEntity<TValue>(int key) where TValue : class, IEntity<int>
  23. {
  24. return GetCachedEntity<int, TValue>(key);
  25. }
  26.  
  27. public void Set<TKey, TValue>(TKey key, TValue value, TimeSpan? slidingExpireTime = null)
  28. {
  29. var cache = CacheManager.GetCache<TKey, TValue>(typeof(TValue).Name);
  30. cache.Set(key, value, slidingExpireTime);
  31. }
  32.  
  33. }

从缓存读取信息的逻辑为:首先从缓存里读取信息,如果未读取到,则从数据库读取对应信息,并且将信息保存到缓存中

  4.修改前端相关代码,为了方便测试,直接在home.js和home.cshtml里添加访问缓存信息的代码,home.js的代码如下

home.cs

  1. (function() {
  2. var controllerId = 'app.views.home';
  3. angular.module('app').controller(controllerId, [
  4. '$scope', 'abp.services.app.cacheTest', function ($scope,service) {
  5. var vm = this;
  6. //Home logic...
  7. vm.article = {};
  8. vm.title = "";
  9. vm.id = 0;
  10. vm.createArticle = function() {
  11. service.createArticle({ title: vm.title }).success(function(result) {
  12. abp.notify.success("文章创建成功");
  13. });
  14. };
  15. vm.getArticle = function() {
  16. service.getArticle({ id:vm.id}).success(function (result) {
  17. vm.article = result;
  18. });
  19. };
  20. }
  21. ]);
  22. })();

home.cshtml

  1. <div ng-controller="app.views.home as vm">
  2. <h1>@L("WellcomeMessage")</h1>
  3. <div class="row">
  4. <div class=" well well-sm">
  5. <div class="row">
  6. <div class="col-md-6">
  7. <input type="text" ng-model="vm.id" class="form-control" placeholder="id" required="" maxlength="32">
  8. </div>
  9. <div class="col-md-6">
  10. <button type="button" ng-click="vm.getArticle()" class="btn btn-primary"><i class="fa fa-sign-in"></i> 读取缓存信息</button>
  11. </div>
  12. </div>
  13. <div>
  14. <div>{{vm.article.id}}</div>
  15. <div>{{vm.article.title}}</div>
  16. </div>
  17. </div>
  18.  
  19. <div class=" well well-sm">
  20. <div class="row">
  21. <div class="col-md-6">
  22. <input ng-model="vm.title" type="text" class="form-control" placeholder="文章标题" required="" maxlength="32">
  23. </div>
  24. <div class="col-md-6">
  25. <button type="button" ng-click="vm.createArticle()" class="btn btn-primary"><i class="fa fa-sign-in"></i> 新建文章</button>
  26. </div>
  27. </div>
  28. </div>
  29.  
  30. </div>
  31. </div>

现在访问页面,看看能否正常添加信息与访问缓存里的信息,我们打开首页,可以看到能够正常添加与访问缓存的信息

  5.现在缓存能够正常的设置与读取了,但是有一个很严重的问题,那就是重启web服务后,无法正常加载缓存,必须要清空缓存,网站才能正常运行,报错信息如下:

Unable to find assembly 'EntityFrameworkDynamicProxies-Abp.Zero, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.

根据错误信息来看,应该是EntityFramework动态生成了实体类的代理类,导致反序列化失败,通过查看ABP的源代码我们可以知道,ABP使用的是BinaryFormatter,动态生成的实体类使用BinaryFormatter是有问题的。要解决这个问题有以下两种方法:

A) 替换ABP的Redis缓存默认实现,不使用BinaryFormatter进行序列化,使用JSON.NET进行序列化

要替换ABP的Redis缓存默认实现修改修改3个地方

  1. 实现一个ICache,可以参考ABP的实现,修改序列化与反序列化的相关代码,序列化与反序列化时需要注意,需要将原始对象包装到RedisCacheItem中,之所以要这样做,是因为反序列化时需要获取原始对象的类型,如果直接反序列化为object对象,有时会直接被反序列化为JObject对象,这时就没法直接转换为原始对象了
  2. 实现一个ICacheManager,可以参考ABP的实现
  3. 将新实现的ICacheManager注册到IOC中

修改后的完整代码如下:

RedisCacheItem.cs

  1. public class RedisCacheItem
  2. {
  3. public Type Type { get; set; }
  4. public string Item { get; set; }
  5. }

RedisCacheManager.cs

  1. public class RedisCacheManager : CacheManagerBase
  2. {
  3. public RedisCacheManager(IIocManager iocManager ICachingConfiguration configuration)
  4. : base(iocManager, configuration)
  5. {
  6. IocManager.RegisterIfNot<RedisCache>(DependencyLifeStyle.Transient);
  7. }
  8. protected override ICache CreateCacheImplementation(string name)
  9. {
  10. return IocManager.Resolve<RedisCache>(new { name });
  11. }
  12. }

RedisCache.cs

  1. public class RedisCache : CacheBase
  2. {
  3. private readonly ConnectionMultiplexer _connectionMultiplexer;
  4. private readonly AbpRedisCacheConfig _config;
  5.  
  6. public IDatabase Database
  7. {
  8. get
  9. {
  10. return _connectionMultiplexer.GetDatabase();
  11. }
  12. }
  13.  
  14. public RedisCache(string name, IAbpRedisConnectionProvider redisConnectionProvider, AbpRedisCacheConfig config)
  15. : base(name)
  16. {
  17. _config = config;
  18. var connectionString = redisConnectionProvider.GetConnectionString(_config.ConnectionStringKey);
  19. _connectionMultiplexer = redisConnectionProvider.GetConnection(connectionString);
  20. }
  21.  
  22. public override object GetOrDefault(string key)
  23. {
  24. var obj = Database.StringGet(GetLocalizedKey(key));
  25. if (obj.HasValue)
  26. {
  27. var item = JsonConvert.DeserializeObject < RedisCacheItem>(obj);
  28. return JsonConvert.DeserializeObject(item.Item, item.Type);
  29. }
  30.  
  31. return null;
  32. }
  33.  
  34. public override void Set(string key, object value, TimeSpan? slidingExpireTime = null)
  35. {
  36. if (value == null)
  37. {
  38. throw new AbpException("Can not insert null values to the cache!");
  39. }
  40.  
  41. var cacheItem = new RedisCacheItem { Type = value.GetType(), Item = JsonConvert.SerializeObject(value) };
  42.  
  43. Database.StringSet(
  44. GetLocalizedKey(key),
  45. JsonConvert.SerializeObject(cacheItem),
  46. slidingExpireTime
  47. );
  48. }
  49.  
  50. public override void Remove(string key)
  51. {
  52. Database.KeyDelete(GetLocalizedKey(key));
  53. }
  54.  
  55. public override void Clear()
  56. {
  57. Database.KeyDeleteWithPrefix(GetLocalizedKey("*"));
  58. }
  59.  
  60. private string GetLocalizedKey(string key)
  61. {
  62. return "n:" + Name + ",c:" + key;
  63. }
  64. }

UsingRedisInAbpApplicationModule.cs

  1. [DependsOn(typeof(UsingRedisInAbpCoreModule), typeof(AbpAutoMapperModule))]
  2. public class UsingRedisInAbpApplicationModule : AbpModule
  3. {
  4. public override void PreInitialize()
  5. {
  6. base.PreInitialize();
  7. //IocManager.Register<ICache,RedisCache>();
  8. IocManager.Register<ICacheManager, RedisCacheManager>();
  9. //如果Redis在本机,并且使用的默认端口,下面的代码可以不要
  10. //Configuration.Modules.AbpRedisCacheModule().ConnectionStringKey = "KeyName";
  11. }
  12.  
  13. public override void Initialize()
  14. {
  15. IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
  16. }
  17. }

B)禁用EntityFramework的动态代理实体类生成功能

直接在UsingRedisInAbpDbContext类的构造函数中添加如下代码

Configuration.ProxyCreationEnabled = false;

此时无论是使用第一种方式还是第二种方式,都能够正常的读取和设置缓存了。

两种修改方式比较:

  1. 使用第一种方式时,会序列化与反序列化两次,性能会受到一定的影响
  2. 使用第二种方式时,实体的导航属性延迟加载功能会受到影响

那么有没有一种方式可以实现只序列化一次,实体的导航属性够延迟加载也不受影响呢?如果一定要实现的话,可以这样做,ABP的默认缓存实现不进行修改,只将我们自己的自定义缓存实现换成访问Redis Cache就行。

目前的缓存还无法自动更新,下一篇将实现原始数据增删改后,同步更新缓存的内容。

本文的完整代码下载地址:http://files.cnblogs.com/files/loyldg/UsingRedisInAbp.src.rar

ABP中使用Redis Cache(1)的更多相关文章

  1. ABP中使用Redis Cache(2)

    上一篇讲解了如何在ABP中使用Redis Cache,虽然能够正常的访问Redis,但是Redis里的信息无法同步更新.本文将讲解如何实现Redis Cache与实体同步更新.要实现数据的同步更新,我 ...

  2. Azure Redis Cache

    将于 2014 年 9 月 1 日停止Azure Shared Cache服务,因此你需要在该日期前迁移到 Azure Redis Cache.Azure Redis Cache包含以下两个层级的产品 ...

  3. Spring Boot 揭秘与实战(二) 数据缓存篇 - Redis Cache

    文章目录 1. Redis Cache 集成 2. 源代码 本文,讲解 Spring Boot 如何集成 Redis Cache,实现缓存. 在阅读「Spring Boot 揭秘与实战(二) 数据缓存 ...

  4. ABP module-zero +AdminLTE+Bootstrap Table+jQuery权限管理系统第十五节--缓存小结与ABP框架项目中 Redis Cache的实现

    返回总目录:ABP+AdminLTE+Bootstrap Table权限管理系统一期 缓存 为什么要用缓存 为什么要用缓存呢,说缓存之前先说使用缓存的优点. 减少寄宿服务器的往返调用(round-tr ...

  5. 缓存与ABP Redis Cache

    缓存与ABP Redis Cache 为什么要用缓存 为什么要用缓存呢,说缓存之前先说使用缓存的优点. 减少寄宿服务器的往返调用(round-trips). 如果缓存在客户端或是代理,将减少对服务器的 ...

  6. 使用abp的 redis cache

    top 使用abp的 redis cache -1. 在微软维护的github项目的release里找到redis的windows版本 64位 大约5M,安装,安装,然后在安装目录找到redis.wi ...

  7. .NET中使用Redis (二)

    很久以前写了一篇文章 .NET中使用Redis 介绍了如何安装Redis服务端,以及如何在.NET中调用Redis读取数据.本文简单介绍如何设计NoSQL数据库,以及如何使用Redis来存储对象. 和 ...

  8. 如何在ASP.NET Core中使用Redis

    注:本文提到的代码示例下载地址> https://code.msdn.microsoft.com/How-to-use-Redis-in-ASPNET-0d826418 Redis是一个开源的内 ...

  9. Azure Redis Cache (1) 入门

    <Windows Azure Platform 系列文章目录> Microsoft Azure Redis Cache基于流行的开源Redis Cache. 1.功能 Redis 是一种高 ...

随机推荐

  1. GitHub的使用记录

    前言: 该贴为笔者在使用GItHub中的一些使用注意,及Git的基本命令,会一直记录笔者在使用GitHub中可能产生的错误及解决方法(会一直更新中),待一些Git初使用者参考,如果有说明不详细或不对的 ...

  2. SQL之case when then用法

    case具有两种格式.简单case函数和case搜索函数. 按 Ctrl+C 复制代码 这两种方式,可以实现相同的功能.简单case函数的写法相对比较简洁,但是和case搜索函数相比,功能方面会有些限 ...

  3. ES6学习记录

    前言 由于要学习React Native ,所以得用到ES6,故为运用React Native做一个铺垫 学习记录 一.变量 1.let let 与 var 作用相同,用于定义变量,但是作用域不同.不 ...

  4. XE2:查看Extended Events收集的数据

    SQL Server 使用Target来存储Events,Target 能够将Events存储到File中(扩展名是 xel),或 memoy buffer 中(Ring Buffer),Event ...

  5. html5的audio在safari(windows)中无效

    因为mac下的safari不会有这样的问题(OSX默认都装的有QuickTime),而windows下用safari的比例实在小不用考虑. apple算是偷了一个小懒.而所谓的需要quicktime并 ...

  6. 数据结构与算法JavaScript (五) 串(经典KMP算法)

    KMP算法和BM算法 KMP是前缀匹配和BM后缀匹配的经典算法,看得出来前缀匹配和后缀匹配的区别就仅仅在于比较的顺序不同 前缀匹配是指:模式串和母串的比较从左到右,模式串的移动也是从 左到右 后缀匹配 ...

  7. Visulalization Voronoi in OpenSceneGraph

    Visulalization Voronoi in OpenSceneGraph eryar@163.com Abstract. In mathematics a Voronoi diagram is ...

  8. WPF 子窗体关闭,刷新父窗体

    父窗体代码 private void DGUserEdit() { if(DGUser.SelectedItem!=null) { DataRow dr = (DGUser.SelectedItem ...

  9. js实现匀速运动及透明度动画

    1.html代码 <body> <div id="container"> <span id="btn"></span& ...

  10. Tomcat报java.lang.OutOfMemoryError: Java heap space错误停止运行如何解决

    最近开发的一个商业项目,部署完成后,经常出现Tomcat挂掉的现象,报的异常是:java.lang.OutOfMemoryError: Java heap space,上网google了一下,了解了一 ...