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就行。

目前的緩存還無法自動更新,下一篇將實現原始數據增刪改后,同步更新緩存的內容。

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM