ABP中使用Redis Cache(1)
本文将讲解如何在ABP中使用Redis Cache以及使用过程中遇到的各种问题。下面就直接讲解使用步骤,Redis环境的搭建请直接网上搜索。
使用步骤:
一、ABP环境搭建
- 到http://www.aspnetboilerplate.com/Templates下载一个ABP项目的模板,项目 类型选择Angularjs+EntityFramework,项目名称为“UsingRedisInAbp”
- 生成数据库,并初始化基本数据。在包管理器的控制台上运行Updata-Database命令,运行时需要注意,默认项目要选中“UsingRedisInAbp.EntityFramework”,启动项目要设置为“UsingRedisInAbp.Web”
- 在nuget里面添加对”Abp.RedisCache”的引用,我引用的0.7.8.1版本
二、替换默认的缓存管理器
修改UsingRedisInAbpApplicationModule类的代码,主要是修改默认缓存管理器和Redis的连接字符串,修改后的完整代码如下:
[DependsOn(typeof(UsingRedisInAbpCoreModule), typeof(AbpAutoMapperModule))]
public class UsingRedisInAbpApplicationModule : AbpModule
{
public override void PreInitialize()
{
base.PreInitialize();
IocManager.Register<ICacheManager, AbpRedisCacheManager>();
//如果Redis在本机,并且使用的默认端口,下面的代码可以不要
//Configuration.Modules.AbpRedisCacheModule().ConnectionStringKey = "KeyName";
} public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
}
}
三、缓存的使用
现在我们编译一下项目,编译通过后我们按F5运行,如果看到如下界面,表示运行成功了
下面我们看看Redis里是否有相关的缓存信息,用Redis Desktop Manager连接Redis,可以看到如下信息,说明信息已经缓存成功了
四、自定义缓存信息的读取与设置
为了演示方便,我们以读取文章信息为例来说明,文章信息只包含一个”Title”字段.
- 在.Core项目里添加文章类,然后在UsingRedisInAbpDbContext类里添加一个Articles属性
- 使用Add-Migration添加信息,然后更新数据库
- 在.Application项目里添加Caching文件夹,并在里面添加读取和设置缓存的通用接口和实现类,实现类CacheService.cs的代码如下:
public class CacheService : ICacheService,ISingletonDependency
{
public ICacheManager CacheManager { get; set; } public TValue GetCachedEntity<TKey, TValue>(TKey key) where TValue : class,IEntity<TKey>
{
var cache = CacheManager.GetCache<TKey, TValue>(typeof(TValue).Name);
var item = cache.Get(key, () =>
{
var repository = IocManager.Instance.Resolve<IRepository<TValue, TKey>>();
var entity = repository.FirstOrDefault(key);
if (entity == null)
{
throw new UserFriendlyException(string.Format("读取的信息不存在,Key:{0}",key));
}
return entity;
}); return item;
} public TValue GetCachedEntity<TValue>(int key) where TValue : class, IEntity<int>
{
return GetCachedEntity<int, TValue>(key);
} public void Set<TKey, TValue>(TKey key, TValue value, TimeSpan? slidingExpireTime = null)
{
var cache = CacheManager.GetCache<TKey, TValue>(typeof(TValue).Name);
cache.Set(key, value, slidingExpireTime);
} }
从缓存读取信息的逻辑为:首先从缓存里读取信息,如果未读取到,则从数据库读取对应信息,并且将信息保存到缓存中
4.修改前端相关代码,为了方便测试,直接在home.js和home.cshtml里添加访问缓存信息的代码,home.js的代码如下
home.cs
(function() {
var controllerId = 'app.views.home';
angular.module('app').controller(controllerId, [
'$scope', 'abp.services.app.cacheTest', function ($scope,service) {
var vm = this;
//Home logic...
vm.article = {};
vm.title = "";
vm.id = 0;
vm.createArticle = function() {
service.createArticle({ title: vm.title }).success(function(result) {
abp.notify.success("文章创建成功");
});
};
vm.getArticle = function() {
service.getArticle({ id:vm.id}).success(function (result) {
vm.article = result;
});
};
}
]);
})();
home.cshtml
<div ng-controller="app.views.home as vm">
<h1>@L("WellcomeMessage")</h1>
<div class="row">
<div class=" well well-sm">
<div class="row">
<div class="col-md-6">
<input type="text" ng-model="vm.id" class="form-control" placeholder="id" required="" maxlength="32">
</div>
<div class="col-md-6">
<button type="button" ng-click="vm.getArticle()" class="btn btn-primary"><i class="fa fa-sign-in"></i> 读取缓存信息</button>
</div>
</div>
<div>
<div>{{vm.article.id}}</div>
<div>{{vm.article.title}}</div>
</div>
</div> <div class=" well well-sm">
<div class="row">
<div class="col-md-6">
<input ng-model="vm.title" type="text" class="form-control" placeholder="文章标题" required="" maxlength="32">
</div>
<div class="col-md-6">
<button type="button" ng-click="vm.createArticle()" class="btn btn-primary"><i class="fa fa-sign-in"></i> 新建文章</button>
</div>
</div>
</div> </div>
</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个地方
- 实现一个ICache,可以参考ABP的实现,修改序列化与反序列化的相关代码,序列化与反序列化时需要注意,需要将原始对象包装到RedisCacheItem中,之所以要这样做,是因为反序列化时需要获取原始对象的类型,如果直接反序列化为object对象,有时会直接被反序列化为JObject对象,这时就没法直接转换为原始对象了
- 实现一个ICacheManager,可以参考ABP的实现
- 将新实现的ICacheManager注册到IOC中
修改后的完整代码如下:
RedisCacheItem.cs
public class RedisCacheItem
{
public Type Type { get; set; }
public string Item { get; set; }
}
RedisCacheManager.cs
public class RedisCacheManager : CacheManagerBase
{
public RedisCacheManager(IIocManager iocManager, ICachingConfiguration configuration)
: base(iocManager, configuration)
{
IocManager.RegisterIfNot<RedisCache>(DependencyLifeStyle.Transient);
}
protected override ICache CreateCacheImplementation(string name)
{
return IocManager.Resolve<RedisCache>(new { name });
}
}
RedisCache.cs
public class RedisCache : CacheBase
{
private readonly ConnectionMultiplexer _connectionMultiplexer;
private readonly AbpRedisCacheConfig _config; public IDatabase Database
{
get
{
return _connectionMultiplexer.GetDatabase();
}
} public RedisCache(string name, IAbpRedisConnectionProvider redisConnectionProvider, AbpRedisCacheConfig config)
: base(name)
{
_config = config;
var connectionString = redisConnectionProvider.GetConnectionString(_config.ConnectionStringKey);
_connectionMultiplexer = redisConnectionProvider.GetConnection(connectionString);
} public override object GetOrDefault(string key)
{
var obj = Database.StringGet(GetLocalizedKey(key));
if (obj.HasValue)
{
var item = JsonConvert.DeserializeObject < RedisCacheItem>(obj);
return JsonConvert.DeserializeObject(item.Item, item.Type);
} return null;
} public override void Set(string key, object value, TimeSpan? slidingExpireTime = null)
{
if (value == null)
{
throw new AbpException("Can not insert null values to the cache!");
} var cacheItem = new RedisCacheItem { Type = value.GetType(), Item = JsonConvert.SerializeObject(value) }; Database.StringSet(
GetLocalizedKey(key),
JsonConvert.SerializeObject(cacheItem),
slidingExpireTime
);
} public override void Remove(string key)
{
Database.KeyDelete(GetLocalizedKey(key));
} public override void Clear()
{
Database.KeyDeleteWithPrefix(GetLocalizedKey("*"));
} private string GetLocalizedKey(string key)
{
return "n:" + Name + ",c:" + key;
}
}
UsingRedisInAbpApplicationModule.cs
[DependsOn(typeof(UsingRedisInAbpCoreModule), typeof(AbpAutoMapperModule))]
public class UsingRedisInAbpApplicationModule : AbpModule
{
public override void PreInitialize()
{
base.PreInitialize();
//IocManager.Register<ICache,RedisCache>();
IocManager.Register<ICacheManager, RedisCacheManager>();
//如果Redis在本机,并且使用的默认端口,下面的代码可以不要
//Configuration.Modules.AbpRedisCacheModule().ConnectionStringKey = "KeyName";
} public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
}
}
B)禁用EntityFramework的动态代理实体类生成功能
直接在UsingRedisInAbpDbContext类的构造函数中添加如下代码
Configuration.ProxyCreationEnabled = false;
此时无论是使用第一种方式还是第二种方式,都能够正常的读取和设置缓存了。
两种修改方式比较:
- 使用第一种方式时,会序列化与反序列化两次,性能会受到一定的影响
- 使用第二种方式时,实体的导航属性延迟加载功能会受到影响
那么有没有一种方式可以实现只序列化一次,实体的导航属性够延迟加载也不受影响呢?如果一定要实现的话,可以这样做,ABP的默认缓存实现不进行修改,只将我们自己的自定义缓存实现换成访问Redis Cache就行。
目前的缓存还无法自动更新,下一篇将实现原始数据增删改后,同步更新缓存的内容。
本文的完整代码下载地址:http://files.cnblogs.com/files/loyldg/UsingRedisInAbp.src.rar
ABP中使用Redis Cache(1)的更多相关文章
- ABP中使用Redis Cache(2)
上一篇讲解了如何在ABP中使用Redis Cache,虽然能够正常的访问Redis,但是Redis里的信息无法同步更新.本文将讲解如何实现Redis Cache与实体同步更新.要实现数据的同步更新,我 ...
- Azure Redis Cache
将于 2014 年 9 月 1 日停止Azure Shared Cache服务,因此你需要在该日期前迁移到 Azure Redis Cache.Azure Redis Cache包含以下两个层级的产品 ...
- Spring Boot 揭秘与实战(二) 数据缓存篇 - Redis Cache
文章目录 1. Redis Cache 集成 2. 源代码 本文,讲解 Spring Boot 如何集成 Redis Cache,实现缓存. 在阅读「Spring Boot 揭秘与实战(二) 数据缓存 ...
- ABP module-zero +AdminLTE+Bootstrap Table+jQuery权限管理系统第十五节--缓存小结与ABP框架项目中 Redis Cache的实现
返回总目录:ABP+AdminLTE+Bootstrap Table权限管理系统一期 缓存 为什么要用缓存 为什么要用缓存呢,说缓存之前先说使用缓存的优点. 减少寄宿服务器的往返调用(round-tr ...
- 缓存与ABP Redis Cache
缓存与ABP Redis Cache 为什么要用缓存 为什么要用缓存呢,说缓存之前先说使用缓存的优点. 减少寄宿服务器的往返调用(round-trips). 如果缓存在客户端或是代理,将减少对服务器的 ...
- 使用abp的 redis cache
top 使用abp的 redis cache -1. 在微软维护的github项目的release里找到redis的windows版本 64位 大约5M,安装,安装,然后在安装目录找到redis.wi ...
- .NET中使用Redis (二)
很久以前写了一篇文章 .NET中使用Redis 介绍了如何安装Redis服务端,以及如何在.NET中调用Redis读取数据.本文简单介绍如何设计NoSQL数据库,以及如何使用Redis来存储对象. 和 ...
- 如何在ASP.NET Core中使用Redis
注:本文提到的代码示例下载地址> https://code.msdn.microsoft.com/How-to-use-Redis-in-ASPNET-0d826418 Redis是一个开源的内 ...
- Azure Redis Cache (1) 入门
<Windows Azure Platform 系列文章目录> Microsoft Azure Redis Cache基于流行的开源Redis Cache. 1.功能 Redis 是一种高 ...
随机推荐
- 在Windows中安装Memcached
Memcached是一个高并发的内存键值对缓存系统,它的主要作用是将数据库查询结果,内容,以及其它一些耗时的计算结果缓存到系统内存中,从而加速Web应用程序的响应速度. Memcached最开始是作为 ...
- EF6 Power Tools的妙用和问题
环境:vs2013+EF:6.1.3.0+Power Tools:Beta 4 power tools:是一个反向工程,在已有数据库的情况下,可以利用它生成Code Frist模式的代码. 问题: 它 ...
- How to use the function of assembly.
Here are some simple conceptions that I summarized: 1, %rsp ----- top of the stack 2, %rbp -- ...
- <dependency>
<dependency> <groupId>org.hibernate</groupId> ...
- junit测试,使用classpath和file 加载文件的区别
用junit测试发现一个问题,怎么加载配置文件?一直都出现这样的错误 ERROR: org.springframework.test.context.TestContextManager - Caug ...
- 调用Child Package
使用Execute Package Task,能够在一个package中调用并执行其他package,被调用的Package称作 Child Package,Execute Package Task ...
- 深入理解DOM事件机制系列第三篇——事件对象
× 目录 [1]获取 [2]事件类型 [3]事件目标[4]事件代理[5]事件冒泡[6]事件流[7]默认行为 前面的话 在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事 ...
- FragmentPagerAdapter+ViewPager实现Tab切换效果
1.Activity 加载布局文件,获取Viewpager控件 给ViewPager填充适配器. import android.app.ActionBar; import android.app ...
- pixi.js教程中文版--基础篇
前言 Pixi.js使用WebGL,是一个超快的HTML5 2D渲染引擎.作为一个Javascript的2D渲染器,Pixi.js的目标是提供一个快速的.轻量级而且是兼任所有设备的2D库.提供无缝 C ...
- 【记录】AutoMapper Project To OrderBy Skip Take 正确写法
AutoMapper:Queryable Extensions 示例代码: using (var context = new orderEntities()) { return context.Ord ...