From e6b44e733085529a31cbaa629eb9b6c18ca8310d Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Fri, 29 Jan 2021 18:32:25 +0800 Subject: [PATCH] Add more "Many" methods to the ICacheSupportsMultipleItems --- .../StackExchangeRedis/AbpRedisCache.cs | 38 ++- .../Volo/Abp/Caching/DistributedCache.cs | 281 ++++++++++++++++++ .../Caching/ICacheSupportsMultipleItems.cs | 16 +- .../Volo/Abp/Caching/IDistributedCache.cs | 63 +++- .../Abp/Caching/TestMemoryDistributedCache.cs | 16 + 5 files changed, 403 insertions(+), 11 deletions(-) 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 c3cb34f30c..dbf04a1d80 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 @@ -45,19 +45,26 @@ namespace Volo.Abp.Caching.StackExchangeRedis MapMetadataMethod = type.GetMethod("MapMetadata", BindingFlags.Instance | BindingFlags.NonPublic); - GetAbsoluteExpirationMethod = type.GetMethod("GetAbsoluteExpiration", BindingFlags.Static | BindingFlags.NonPublic); + GetAbsoluteExpirationMethod = + type.GetMethod("GetAbsoluteExpiration", BindingFlags.Static | BindingFlags.NonPublic); - GetExpirationInSecondsMethod = type.GetMethod("GetExpirationInSeconds", BindingFlags.Static | BindingFlags.NonPublic); + GetExpirationInSecondsMethod = + type.GetMethod("GetExpirationInSeconds", BindingFlags.Static | BindingFlags.NonPublic); - SetScript = type.GetField("SetScript", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null).ToString(); + SetScript = type.GetField("SetScript", BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null) + .ToString(); - AbsoluteExpirationKey = type.GetField("AbsoluteExpirationKey", 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(); + SlidingExpirationKey = type.GetField("SlidingExpirationKey", BindingFlags.Static | BindingFlags.NonPublic) + ?.GetValue(null).ToString(); - DataKey = type.GetField("DataKey", 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(); + // ReSharper disable once PossibleNullReferenceException + NotPresent = type.GetField("NotPresent", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null) + .To(); } public AbpRedisCache(IOptions optionsAccessor) @@ -124,6 +131,23 @@ namespace Volo.Abp.Caching.StackExchangeRedis await Task.WhenAll(PipelineSetMany(items, options)); } + public void RefreshMany( + IEnumerable keys) + { + keys = Check.NotNull(keys, nameof(keys)); + + GetAndRefreshMany(keys, false); + } + + public async Task RefreshManyAsync( + IEnumerable keys, + CancellationToken token = default) + { + keys = Check.NotNull(keys, nameof(keys)); + + await GetAndRefreshManyAsync(keys, false, token); + } + protected virtual byte[][] GetAndRefreshMany( IEnumerable keys, bool getData) diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs index d62411628e..2ebe9b2fb4 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs @@ -527,6 +527,198 @@ namespace Volo.Abp.Caching return value; } + public KeyValuePair[] GetOrAddMany( + IEnumerable keys, + Func, IEnumerable>> factory, + Func optionsFactory = null, + bool? hideErrors = null, + bool considerUow = false) + { + + KeyValuePair[] result; + var keyArray = keys.ToArray(); + + var cacheSupportsMultipleItems = Cache as ICacheSupportsMultipleItems; + if (cacheSupportsMultipleItems == null) + { + result = GetManyFallback( + keyArray, + hideErrors, + considerUow + ); + } + else + { + var notCachedKeys = new List(); + var cachedValues = new List>(); + if (ShouldConsiderUow(considerUow)) + { + var uowCache = GetUnitOfWorkCache(); + foreach (var key in keyArray) + { + var value = uowCache.GetOrDefault(key)?.GetUnRemovedValueOrNull(); + if (value != null) + { + cachedValues.Add(new KeyValuePair(key, value)); + } + } + + notCachedKeys = keyArray.Except(cachedValues.Select(x => x.Key)).ToList(); + if (!notCachedKeys.Any()) + { + return cachedValues.ToArray(); + } + } + + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + byte[][] cachedBytes; + + var readKeys = notCachedKeys.Any() ? notCachedKeys.ToArray() : keyArray; + try + { + cachedBytes = cacheSupportsMultipleItems.GetMany(readKeys.Select(NormalizeKey)); + } + catch (Exception ex) + { + if (hideErrors == true) + { + HandleException(ex); + return ToCacheItemsWithDefaultValues(keyArray); + } + + throw; + } + + result = cachedValues.Concat(ToCacheItems(cachedBytes, readKeys)).ToArray(); + } + + if (result.All(x => x.Value != null)) + { + return result; + } + + var missingKeys = new List(); + var missingValuesIndex = new List(); + for (var i = 0; i < keyArray.Length; i++) + { + if (result[i].Value != null) + { + continue; + } + + missingKeys.Add(keyArray[i]); + missingValuesIndex.Add(i); + } + + var missingValues = factory.Invoke(missingKeys).ToArray(); + var valueQueue = new Queue>(missingValues); + + SetMany(missingValues, optionsFactory?.Invoke(), hideErrors, considerUow); + + foreach (var index in missingValuesIndex) + { + result[index] = valueQueue.Dequeue(); + } + + return result; + } + + + public async Task[]> GetOrAddManyAsync( + IEnumerable keys, + Func, Task>>> factory, + Func optionsFactory = null, + bool? hideErrors = null, + bool considerUow = false, + CancellationToken token = default) + { + KeyValuePair[] result; + var keyArray = keys.ToArray(); + + var cacheSupportsMultipleItems = Cache as ICacheSupportsMultipleItems; + if (cacheSupportsMultipleItems == null) + { + result = await GetManyFallbackAsync( + keyArray, + hideErrors, + considerUow, token); + } + else + { + var notCachedKeys = new List(); + var cachedValues = new List>(); + if (ShouldConsiderUow(considerUow)) + { + var uowCache = GetUnitOfWorkCache(); + foreach (var key in keyArray) + { + var value = uowCache.GetOrDefault(key)?.GetUnRemovedValueOrNull(); + if (value != null) + { + cachedValues.Add(new KeyValuePair(key, value)); + } + } + + notCachedKeys = keyArray.Except(cachedValues.Select(x => x.Key)).ToList(); + if (!notCachedKeys.Any()) + { + return cachedValues.ToArray(); + } + } + + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + byte[][] cachedBytes; + + var readKeys = notCachedKeys.Any() ? notCachedKeys.ToArray() : keyArray; + try + { + cachedBytes = await cacheSupportsMultipleItems.GetManyAsync(readKeys.Select(NormalizeKey), token); + } + catch (Exception ex) + { + if (hideErrors == true) + { + await HandleExceptionAsync(ex); + return ToCacheItemsWithDefaultValues(keyArray); + } + + throw; + } + + result = cachedValues.Concat(ToCacheItems(cachedBytes, readKeys)).ToArray(); + } + + if (result.All(x => x.Value != null)) + { + return result; + } + + var missingKeys = new List(); + var missingValuesIndex = new List(); + for (var i = 0; i < keyArray.Length; i++) + { + if (result[i].Value != null) + { + continue; + } + + missingKeys.Add(keyArray[i]); + missingValuesIndex.Add(i); + } + + var missingValues = (await factory.Invoke(missingKeys)).ToArray(); + var valueQueue = new Queue>(missingValues); + + await SetManyAsync(missingValues, optionsFactory?.Invoke(), hideErrors, considerUow, token); + + foreach (var index in missingValuesIndex) + { + result[index] = valueQueue.Dequeue(); + } + + return result; + } + /// /// Sets the cache item value for the provided key. /// @@ -924,6 +1116,71 @@ namespace Volo.Abp.Caching } } + public virtual void RefreshMany( + IEnumerable keys, + bool? hideErrors = null) + { + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + if (Cache is ICacheSupportsMultipleItems cacheSupportsMultipleItems) + { + cacheSupportsMultipleItems.RefreshMany(keys.Select(NormalizeKey)); + } + else + { + foreach (var key in keys) + { + Cache.Refresh(NormalizeKey(key)); + } + } + } + catch (Exception ex) + { + if (hideErrors == true) + { + HandleException(ex); + return; + } + + throw; + } + } + + public virtual async Task RefreshManyAsync( + IEnumerable keys, + bool? hideErrors = null, + CancellationToken token = default) + { + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + if (Cache is ICacheSupportsMultipleItems cacheSupportsMultipleItems) + { + await cacheSupportsMultipleItems.RefreshManyAsync(keys.Select(NormalizeKey), token); + } + else + { + foreach (var key in keys) + { + await Cache.RefreshAsync(NormalizeKey(key), token); + } + } + } + catch (Exception ex) + { + if (hideErrors == true) + { + await HandleExceptionAsync(ex); + return; + } + + throw; + } + } + /// /// Removes the cache item for given key from cache. /// @@ -1027,6 +1284,30 @@ namespace Volo.Abp.Caching } } + protected virtual KeyValuePair[] GetOrAddMany( + TCacheKey[] keys, + KeyValuePair[] values, + KeyValuePair[] missingValues) + { + var missingValuesIndex = new List(); + for (var i = 0; i < keys.Length; i++) + { + if (values[i].Value == null) + { + missingValuesIndex.Add(i); + } + } + + var valueQueue = new Queue>(missingValues); + + foreach (var index in missingValuesIndex) + { + values[index] = valueQueue.Dequeue(); + } + + return values; + } + protected virtual void HandleException(Exception ex) { _ = HandleExceptionAsync(ex); diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/ICacheSupportsMultipleItems.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/ICacheSupportsMultipleItems.cs index af6f87021b..ec73a159a4 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/ICacheSupportsMultipleItems.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/ICacheSupportsMultipleItems.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Distributed; @@ -25,6 +26,15 @@ namespace Volo.Abp.Caching IEnumerable> items, DistributedCacheEntryOptions options, CancellationToken token = default - ); + ); + + void RefreshMany( + IEnumerable keys + ); + + Task RefreshManyAsync( + IEnumerable keys, + CancellationToken token = default + ); } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs index 2f74fd678b..d8510efe43 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs @@ -14,8 +14,8 @@ namespace Volo.Abp.Caching public interface IDistributedCache : IDistributedCache where TCacheItem : class { - } + /// /// Represents a distributed cache of type. /// Uses a generic cache key type of type. @@ -128,6 +128,44 @@ namespace Volo.Abp.Caching CancellationToken token = default ); + /// + /// Gets or Adds multiple cache items with the given keys. If any cache items not found for the given keys then adds cache items + /// provided by delegate and returns the provided cache items. + /// + /// The keys of cached items to be retrieved from the cache. + /// The factory delegate is used to provide the cache items when no cache items are found for the given . + /// The cache options for the factory delegate. + /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. + /// The cache items. + KeyValuePair[] GetOrAddMany( + IEnumerable keys, + Func, IEnumerable>> factory, + Func optionsFactory = null, + bool? hideErrors = null, + bool considerUow = false + ); + + /// + /// Gets or Adds multiple cache items with the given keys. If any cache items not found for the given keys then adds cache items + /// provided by delegate and returns the provided cache items. + /// + /// The keys of cached items to be retrieved from the cache. + /// The factory delegate is used to provide the cache items when no cache items are found for the given . + /// The cache options for the factory delegate. + /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. + /// The for the task. + /// The cache items. + Task[]> GetOrAddManyAsync( + IEnumerable keys, + Func, Task>>> factory, + Func optionsFactory = null, + bool? hideErrors = null, + bool considerUow = false, + CancellationToken token = default + ); + /// /// Sets the cache item value for the provided key. /// @@ -219,6 +257,29 @@ namespace Volo.Abp.Caching CancellationToken token = default ); + /// + /// Refreshes multiple cache items. + /// Based on the implementation, this can be more efficient than setting multiple items individually. + /// + /// The keys of cached items to be retrieved from the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. + void RefreshMany( + IEnumerable keys, + bool? hideErrors = null); + + /// + /// Refreshes multiple cache items. + /// Based on the implementation, this can be more efficient than setting multiple items individually. + /// + /// The keys of cached items to be retrieved from the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. + /// The for the task. + /// The indicating that the operation is asynchronous. + Task RefreshManyAsync( + IEnumerable keys, + bool? hideErrors = null, + CancellationToken token = default); + /// /// Removes the cache item for given key from cache. /// diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/TestMemoryDistributedCache.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/TestMemoryDistributedCache.cs index 69589d97cd..4490a50fd3 100644 --- a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/TestMemoryDistributedCache.cs +++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/TestMemoryDistributedCache.cs @@ -57,5 +57,21 @@ namespace Volo.Abp.Caching await SetAsync(item.Key, item.Value, options, token); } } + + public void RefreshMany(IEnumerable keys) + { + foreach (var key in keys) + { + Refresh(key); + } + } + + public async Task RefreshManyAsync(IEnumerable keys, CancellationToken token = default) + { + foreach (var key in keys) + { + await RefreshAsync(key, token); + } + } } }