From 018e7cbd0807b5bda5b23654de2a7ab7d3d8a707 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Sat, 27 Jun 2020 10:23:56 +0800 Subject: [PATCH] Refactored --- .../Caching/StackExchangeRedis/RedisCache.cs | 496 ------------------ .../StackExchangeRedis/RedisExtensions.cs | 70 --- .../StackExchangeRedis/AbpRedisCache.cs | 116 +++- 3 files changed, 106 insertions(+), 576 deletions(-) delete mode 100644 framework/src/Volo.Abp.Caching.StackExchangeRedis/Microsoft/Extensions/Caching/StackExchangeRedis/RedisCache.cs delete mode 100644 framework/src/Volo.Abp.Caching.StackExchangeRedis/Microsoft/Extensions/Caching/StackExchangeRedis/RedisExtensions.cs diff --git a/framework/src/Volo.Abp.Caching.StackExchangeRedis/Microsoft/Extensions/Caching/StackExchangeRedis/RedisCache.cs b/framework/src/Volo.Abp.Caching.StackExchangeRedis/Microsoft/Extensions/Caching/StackExchangeRedis/RedisCache.cs deleted file mode 100644 index 32cc85011b..0000000000 --- a/framework/src/Volo.Abp.Caching.StackExchangeRedis/Microsoft/Extensions/Caching/StackExchangeRedis/RedisCache.cs +++ /dev/null @@ -1,496 +0,0 @@ -// This software is part of the DOTNET extensions -// Copyright (c) .NET Foundation and Contributors -// https://dotnet.microsoft.com/ -// -// All rights reserved. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Caching.Distributed; -using Microsoft.Extensions.Options; -using StackExchange.Redis; - -namespace Microsoft.Extensions.Caching.StackExchangeRedis -{ - public class RedisCache : IDistributedCache, IDisposable - { - // KEYS[1] = = key - // ARGV[1] = absolute-expiration - ticks as long (-1 for none) - // ARGV[2] = sliding-expiration - ticks as long (-1 for none) - // ARGV[3] = relative-expiration (long, in seconds, -1 for none) - Min(absolute-expiration - Now, sliding-expiration) - // ARGV[4] = data - byte[] - // this order should not change LUA script depends on it - protected const string SetScript = (@" - redis.call('HMSET', KEYS[1], 'absexp', ARGV[1], 'sldexp', ARGV[2], 'data', ARGV[4]) - if ARGV[3] ~= '-1' then - redis.call('EXPIRE', KEYS[1], ARGV[3]) - end - return 1"); - - protected const string AbsoluteExpirationKey = "absexp"; - protected const string SlidingExpirationKey = "sldexp"; - protected const string DataKey = "data"; - protected const long NotPresent = -1; - - protected volatile ConnectionMultiplexer Connection; - protected IDatabase Cache; - - protected readonly RedisCacheOptions Options; - protected readonly string Instance; - - protected readonly SemaphoreSlim ConnectionLock = new SemaphoreSlim(initialCount: 1, maxCount: 1); - - public RedisCache(IOptions optionsAccessor) - { - if (optionsAccessor == null) - { - throw new ArgumentNullException(nameof(optionsAccessor)); - } - - Options = optionsAccessor.Value; - - // This allows partitioning a single backend cache for use with multiple apps/services. - Instance = Options.InstanceName ?? string.Empty; - } - - public virtual byte[] Get(string key) - { - if (key == null) - { - throw new ArgumentNullException(nameof(key)); - } - - return GetAndRefresh(key, getData: true); - } - - public virtual async Task GetAsync( - string key, - CancellationToken token = default) - { - if (key == null) - { - throw new ArgumentNullException(nameof(key)); - } - - token.ThrowIfCancellationRequested(); - - return await GetAndRefreshAsync(key, getData: true, token: token); - } - - public virtual void Set( - string key, - byte[] value, - DistributedCacheEntryOptions options) - { - if (key == null) - { - throw new ArgumentNullException(nameof(key)); - } - - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - Connect(); - - var creationTime = DateTimeOffset.UtcNow; - - var absoluteExpiration = GetAbsoluteExpiration(creationTime, options); - - Cache.ScriptEvaluate(SetScript, new RedisKey[] {Instance + key}, - new RedisValue[] - { - absoluteExpiration?.Ticks ?? NotPresent, - options.SlidingExpiration?.Ticks ?? NotPresent, - GetExpirationInSeconds(creationTime, absoluteExpiration, options) ?? NotPresent, - value - }); - } - - public virtual async Task SetAsync( - string key, - byte[] value, - DistributedCacheEntryOptions options, - CancellationToken token = default) - { - if (key == null) - { - throw new ArgumentNullException(nameof(key)); - } - - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - token.ThrowIfCancellationRequested(); - - await ConnectAsync(token); - - var creationTime = DateTimeOffset.UtcNow; - - var absoluteExpiration = GetAbsoluteExpiration(creationTime, options); - - await Cache.ScriptEvaluateAsync(SetScript, new RedisKey[] {Instance + key}, - new RedisValue[] - { - absoluteExpiration?.Ticks ?? NotPresent, - options.SlidingExpiration?.Ticks ?? NotPresent, - GetExpirationInSeconds(creationTime, absoluteExpiration, options) ?? NotPresent, - value - }); - } - - public virtual void Refresh(string key) - { - if (key == null) - { - throw new ArgumentNullException(nameof(key)); - } - - GetAndRefresh(key, getData: false); - } - - public virtual async Task RefreshAsync( - string key, - CancellationToken token = default) - { - if (key == null) - { - throw new ArgumentNullException(nameof(key)); - } - - token.ThrowIfCancellationRequested(); - - await GetAndRefreshAsync(key, getData: false, token: token); - } - - protected virtual void Connect() - { - if (Cache != null) - { - return; - } - - ConnectionLock.Wait(); - try - { - if (Cache == null) - { - if (Options.ConfigurationOptions != null) - { - Connection = ConnectionMultiplexer.Connect(Options.ConfigurationOptions); - } - else - { - Connection = ConnectionMultiplexer.Connect(Options.Configuration); - } - - Cache = Connection.GetDatabase(); - } - } - finally - { - ConnectionLock.Release(); - } - } - - protected virtual async Task ConnectAsync(CancellationToken token = default) - { - token.ThrowIfCancellationRequested(); - - if (Cache != null) - { - return; - } - - await ConnectionLock.WaitAsync(token); - try - { - if (Cache == null) - { - if (Options.ConfigurationOptions != null) - { - Connection = await ConnectionMultiplexer.ConnectAsync(Options.ConfigurationOptions); - } - else - { - Connection = await ConnectionMultiplexer.ConnectAsync(Options.Configuration); - } - - Cache = Connection.GetDatabase(); - } - } - finally - { - ConnectionLock.Release(); - } - } - - protected virtual byte[] GetAndRefresh(string key, bool getData) - { - if (key == null) - { - throw new ArgumentNullException(nameof(key)); - } - - Connect(); - - // This also resets the LRU status as desired. - // TODO: Can this be done in one operation on the server side? Probably, the trick would just be the DateTimeOffset math. - RedisValue[] results; - if (getData) - { - results = Cache.HashMemberGet(Instance + key, AbsoluteExpirationKey, SlidingExpirationKey, DataKey); - } - else - { - results = Cache.HashMemberGet(Instance + key, AbsoluteExpirationKey, SlidingExpirationKey); - } - - // TODO: Error handling - if (results.Length >= 2) - { - MapMetadata(results, out DateTimeOffset? absExpr, out TimeSpan? sldExpr); - Refresh(key, absExpr, sldExpr); - } - - if (results.Length >= 3 && results[2].HasValue) - { - return results[2]; - } - - return null; - } - - protected virtual async Task GetAndRefreshAsync( - string key, - bool getData, - CancellationToken token = default) - { - if (key == null) - { - throw new ArgumentNullException(nameof(key)); - } - - token.ThrowIfCancellationRequested(); - - await ConnectAsync(token); - - // This also resets the LRU status as desired. - // TODO: Can this be done in one operation on the server side? Probably, the trick would just be the DateTimeOffset math. - RedisValue[] results; - if (getData) - { - results = await Cache.HashMemberGetAsync(Instance + key, AbsoluteExpirationKey, SlidingExpirationKey, - DataKey); - } - else - { - results = await Cache.HashMemberGetAsync(Instance + key, AbsoluteExpirationKey, SlidingExpirationKey); - } - - // TODO: Error handling - if (results.Length >= 2) - { - MapMetadata(results, out DateTimeOffset? absExpr, out TimeSpan? sldExpr); - await RefreshAsync(key, absExpr, sldExpr, token); - } - - if (results.Length >= 3 && results[2].HasValue) - { - return results[2]; - } - - return null; - } - - public virtual void Remove(string key) - { - if (key == null) - { - throw new ArgumentNullException(nameof(key)); - } - - Connect(); - - Cache.KeyDelete(Instance + key); - // TODO: Error handling - } - - public virtual async Task RemoveAsync( - string key, - CancellationToken token = default) - { - if (key == null) - { - throw new ArgumentNullException(nameof(key)); - } - - await ConnectAsync(token); - - await Cache.KeyDeleteAsync(Instance + key); - // TODO: Error handling - } - - protected virtual void MapMetadata( - RedisValue[] results, - out DateTimeOffset? absoluteExpiration, - out TimeSpan? slidingExpiration) - { - absoluteExpiration = null; - slidingExpiration = null; - var absoluteExpirationTicks = (long?) results[0]; - if (absoluteExpirationTicks.HasValue && absoluteExpirationTicks.Value != NotPresent) - { - absoluteExpiration = new DateTimeOffset(absoluteExpirationTicks.Value, TimeSpan.Zero); - } - - var slidingExpirationTicks = (long?) results[1]; - if (slidingExpirationTicks.HasValue && slidingExpirationTicks.Value != NotPresent) - { - slidingExpiration = new TimeSpan(slidingExpirationTicks.Value); - } - } - - protected virtual void Refresh( - string key, - DateTimeOffset? absExpr, - TimeSpan? sldExpr) - { - if (key == null) - { - throw new ArgumentNullException(nameof(key)); - } - - // Note Refresh has no effect if there is just an absolute expiration (or neither). - TimeSpan? expr = null; - if (sldExpr.HasValue) - { - if (absExpr.HasValue) - { - var relExpr = absExpr.Value - DateTimeOffset.Now; - expr = relExpr <= sldExpr.Value ? relExpr : sldExpr; - } - else - { - expr = sldExpr; - } - - Cache.KeyExpire(Instance + key, expr); - // TODO: Error handling - } - } - - protected virtual async Task RefreshAsync( - string key, - DateTimeOffset? absExpr, - TimeSpan? sldExpr, - CancellationToken token = default) - { - if (key == null) - { - throw new ArgumentNullException(nameof(key)); - } - - token.ThrowIfCancellationRequested(); - - // Note Refresh has no effect if there is just an absolute expiration (or neither). - TimeSpan? expr = null; - if (sldExpr.HasValue) - { - if (absExpr.HasValue) - { - var relExpr = absExpr.Value - DateTimeOffset.Now; - expr = relExpr <= sldExpr.Value ? relExpr : sldExpr; - } - else - { - expr = sldExpr; - } - - await Cache.KeyExpireAsync(Instance + key, expr); - // TODO: Error handling - } - } - - protected static long? GetExpirationInSeconds( - DateTimeOffset creationTime, - DateTimeOffset? absoluteExpiration, - DistributedCacheEntryOptions options) - { - if (absoluteExpiration.HasValue && options.SlidingExpiration.HasValue) - { - return (long) Math.Min( - (absoluteExpiration.Value - creationTime).TotalSeconds, - options.SlidingExpiration.Value.TotalSeconds); - } - else if (absoluteExpiration.HasValue) - { - return (long) (absoluteExpiration.Value - creationTime).TotalSeconds; - } - else if (options.SlidingExpiration.HasValue) - { - return (long) options.SlidingExpiration.Value.TotalSeconds; - } - - return null; - } - - protected static DateTimeOffset? GetAbsoluteExpiration( - DateTimeOffset creationTime, - DistributedCacheEntryOptions options) - { - if (options.AbsoluteExpiration.HasValue && options.AbsoluteExpiration <= creationTime) - { - throw new ArgumentOutOfRangeException( - nameof(DistributedCacheEntryOptions.AbsoluteExpiration), - options.AbsoluteExpiration.Value, - "The absolute expiration value must be in the future."); - } - - var absoluteExpiration = options.AbsoluteExpiration; - if (options.AbsoluteExpirationRelativeToNow.HasValue) - { - absoluteExpiration = creationTime + options.AbsoluteExpirationRelativeToNow; - } - - return absoluteExpiration; - } - - public virtual void Dispose() - { - Connection?.Close(); - } - } -} diff --git a/framework/src/Volo.Abp.Caching.StackExchangeRedis/Microsoft/Extensions/Caching/StackExchangeRedis/RedisExtensions.cs b/framework/src/Volo.Abp.Caching.StackExchangeRedis/Microsoft/Extensions/Caching/StackExchangeRedis/RedisExtensions.cs deleted file mode 100644 index c83c8df7ff..0000000000 --- a/framework/src/Volo.Abp.Caching.StackExchangeRedis/Microsoft/Extensions/Caching/StackExchangeRedis/RedisExtensions.cs +++ /dev/null @@ -1,70 +0,0 @@ -// This software is part of the DOTNET extensions -// Copyright (c) .NET Foundation and Contributors -// https://dotnet.microsoft.com/ -// -// All rights reserved. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -using System.Threading.Tasks; -using StackExchange.Redis; - -namespace Microsoft.Extensions.Caching.StackExchangeRedis -{ - internal static class RedisExtensions - { - private const string HmGetScript = (@"return redis.call('HMGET', KEYS[1], unpack(ARGV))"); - - internal static RedisValue[] HashMemberGet(this IDatabase cache, string key, params string[] members) - { - var result = cache.ScriptEvaluate( - HmGetScript, - new RedisKey[] { key }, - GetRedisMembers(members)); - - // TODO: Error checking? - return (RedisValue[])result; - } - - internal static async Task HashMemberGetAsync( - this IDatabase cache, - string key, - params string[] members) - { - var result = await cache.ScriptEvaluateAsync( - HmGetScript, - new RedisKey[] { key }, - GetRedisMembers(members)).ConfigureAwait(false); - - // TODO: Error checking? - return (RedisValue[])result; - } - - private static RedisValue[] GetRedisMembers(params string[] members) - { - var redisMembers = new RedisValue[members.Length]; - for (int i = 0; i < members.Length; i++) - { - redisMembers[i] = (RedisValue)members[i]; - } - - return redisMembers; - } - } -} diff --git a/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache.cs b/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache.cs index 86fd3441ab..c3cb34f30c 100644 --- a/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache.cs +++ b/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Distributed; @@ -14,9 +15,75 @@ namespace Volo.Abp.Caching.StackExchangeRedis [DisableConventionalRegistration] public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems { + protected static readonly string SetScript; + protected static readonly string AbsoluteExpirationKey; + protected static readonly string SlidingExpirationKey; + protected static readonly string DataKey; + protected static readonly long NotPresent; + + private static readonly FieldInfo RedisDatabaseField; + private static readonly MethodInfo ConnectMethod; + private static readonly MethodInfo ConnectAsyncMethod; + private static readonly MethodInfo MapMetadataMethod; + private static readonly MethodInfo GetAbsoluteExpirationMethod; + private static readonly MethodInfo GetExpirationInSecondsMethod; + + protected IDatabase RedisDatabase => GetRedisDatabase(); + private IDatabase _redisDatabase; + + protected string Instance { get; } + + static AbpRedisCache() + { + var type = typeof(RedisCache); + + RedisDatabaseField = type.GetField("_cache", BindingFlags.Instance | BindingFlags.NonPublic); + + ConnectMethod = type.GetMethod("Connect", BindingFlags.Instance | BindingFlags.NonPublic); + + ConnectAsyncMethod = type.GetMethod("ConnectAsync", BindingFlags.Instance | BindingFlags.NonPublic); + + MapMetadataMethod = type.GetMethod("MapMetadata", BindingFlags.Instance | BindingFlags.NonPublic); + + GetAbsoluteExpirationMethod = type.GetMethod("GetAbsoluteExpiration", BindingFlags.Static | BindingFlags.NonPublic); + + GetExpirationInSecondsMethod = type.GetMethod("GetExpirationInSeconds", BindingFlags.Static | BindingFlags.NonPublic); + + SetScript = type.GetField("SetScript", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null).ToString(); + + AbsoluteExpirationKey = type.GetField("AbsoluteExpirationKey", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null).ToString(); + + SlidingExpirationKey = type.GetField("SlidingExpirationKey", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null).ToString(); + + DataKey = type.GetField("DataKey", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null).ToString(); + + NotPresent = type.GetField("NotPresent", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null).To(); + } + public AbpRedisCache(IOptions optionsAccessor) : base(optionsAccessor) { + Instance = optionsAccessor.Value.InstanceName ?? string.Empty; + } + + protected virtual void Connect() + { + if (GetRedisDatabase() != null) + { + return; + } + + ConnectMethod.Invoke(this, Array.Empty()); + } + + protected virtual Task ConnectAsync(CancellationToken token = default) + { + if (GetRedisDatabase() != null) + { + return Task.CompletedTask; + } + + return (Task) ConnectAsyncMethod.Invoke(this, new object[] {token}); } public byte[][] GetMany( @@ -68,12 +135,12 @@ namespace Volo.Abp.Caching.StackExchangeRedis if (getData) { - results = Cache.HashMemberGetMany(keyArray, AbsoluteExpirationKey, + results = RedisDatabase.HashMemberGetMany(keyArray, AbsoluteExpirationKey, SlidingExpirationKey, DataKey); } else { - results = Cache.HashMemberGetMany(keyArray, AbsoluteExpirationKey, + results = RedisDatabase.HashMemberGetMany(keyArray, AbsoluteExpirationKey, SlidingExpirationKey); } @@ -96,12 +163,12 @@ namespace Volo.Abp.Caching.StackExchangeRedis if (getData) { - results = await Cache.HashMemberGetManyAsync(keyArray, AbsoluteExpirationKey, + results = await RedisDatabase.HashMemberGetManyAsync(keyArray, AbsoluteExpirationKey, SlidingExpirationKey, DataKey); } else { - results = await Cache.HashMemberGetManyAsync(keyArray, AbsoluteExpirationKey, + results = await RedisDatabase.HashMemberGetManyAsync(keyArray, AbsoluteExpirationKey, SlidingExpirationKey); } @@ -110,13 +177,14 @@ namespace Volo.Abp.Caching.StackExchangeRedis return bytes; } - private Task[] PipelineRefreshManyAndOutData( + protected virtual Task[] PipelineRefreshManyAndOutData( string[] keys, RedisValue[][] results, out byte[][] bytes) { bytes = new byte[keys.Length][]; var tasks = new Task[keys.Length]; + for (var i = 0; i < keys.Length; i++) { if (results[i].Length >= 2) @@ -137,7 +205,7 @@ namespace Volo.Abp.Caching.StackExchangeRedis expr = sldExpr; } - tasks[i] = Cache.KeyExpireAsync(keys[i], expr); + tasks[i] = RedisDatabase.KeyExpireAsync(keys[i], expr); } else { @@ -158,7 +226,7 @@ namespace Volo.Abp.Caching.StackExchangeRedis return tasks; } - private Task[] PipelineSetMany( + protected virtual Task[] PipelineSetMany( IEnumerable> items, DistributedCacheEntryOptions options) { @@ -172,7 +240,7 @@ namespace Volo.Abp.Caching.StackExchangeRedis for (var i = 0; i < itemArray.Length; i++) { - tasks[i] = Cache.ScriptEvaluateAsync(SetScript, new RedisKey[] {Instance + itemArray[i].Key}, + tasks[i] = RedisDatabase.ScriptEvaluateAsync(SetScript, new RedisKey[] {Instance + itemArray[i].Key}, new RedisValue[] { absoluteExpiration?.Ticks ?? NotPresent, @@ -184,12 +252,40 @@ namespace Volo.Abp.Caching.StackExchangeRedis return tasks; } - + + protected virtual void MapMetadata( + RedisValue[] results, + out DateTimeOffset? absoluteExpiration, + out TimeSpan? slidingExpiration) + { + var parameters = new object[] {results, null, null}; + MapMetadataMethod.Invoke(this, parameters); + + absoluteExpiration = (DateTimeOffset?) parameters[1]; + slidingExpiration = (TimeSpan?) parameters[2]; + } + + protected virtual long? GetExpirationInSeconds( + DateTimeOffset creationTime, + DateTimeOffset? absoluteExpiration, + DistributedCacheEntryOptions options) + { + return (long?) GetExpirationInSecondsMethod.Invoke(null, + new object[] {creationTime, absoluteExpiration, options}); + } + + protected virtual DateTimeOffset? GetAbsoluteExpiration( + DateTimeOffset creationTime, + DistributedCacheEntryOptions options) + { + return (DateTimeOffset?) GetAbsoluteExpirationMethod.Invoke(null, new object[] {creationTime, options}); + } + private IDatabase GetRedisDatabase() { if (_redisDatabase == null) { - _redisDatabase = RedisDatabaseField.GetValue(this) as IDatabase; + _redisDatabase = RedisDatabaseField.GetValue(this) as IDatabase; } return _redisDatabase;