zoukankan      html  css  js  c++  java
  • ABP中使用Redis Cache(1)

    本文将讲解如何在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的连接字符串,修改后的完整代码如下:

    [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());
            }
        }
    View Code

    三、缓存的使用

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

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

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

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

    1. 在.Core项目里添加文章类,然后在UsingRedisInAbpDbContext类里添加一个Articles属性
    2. 使用Add-Migration添加信息,然后更新数据库
    3. 在.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);
            }
    
        }
    View Code

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

      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;
                    });
                };
            }
        ]);
    })();
    View Code

    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>
    View Code

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

      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

    public class RedisCacheItem
        {
            public Type Type { get; set; }
            public string Item { get; set; }
        }
    View Code

    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 });
            }
        }
    View Code

    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;
            }
    } 
    View Code

    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());
            }
        }
    View Code

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

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

    Configuration.ProxyCreationEnabled = false;

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

    两种修改方式比较:

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

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

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

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

    作者:loyldg
    出处:http://www.cnblogs.com/loyldg/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如有问题,可以邮件:loyldg@126.com  联系我,非常感谢。

  • 相关阅读:
    1.1图形验证码的生成
    1.3蓝图导入问题
    1.2数据库迁移
    1.1首页显示
    1.3 日志配置以及蓝图的抽取
    1.2 app工厂以及db问题的解决
    每日总结
    每日总结
    每日总结
    每日总结
  • 原文地址:https://www.cnblogs.com/loyldg/p/using-redis-in-abp-1.html
Copyright © 2011-2022 走看看