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 267ab259f5..5bb1dd2481 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs @@ -1,6 +1,9 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -28,16 +31,16 @@ namespace Volo.Abp.Caching IDistributedCacheSerializer serializer, IDistributedCacheKeyNormalizer keyNormalizer, IHybridServiceScopeFactory serviceScopeFactory) : base( - distributedCacheOption: distributedCacheOption, - cache: cache, - cancellationTokenProvider: cancellationTokenProvider, - serializer: serializer, - keyNormalizer: keyNormalizer, - serviceScopeFactory: serviceScopeFactory) + distributedCacheOption: distributedCacheOption, + cache: cache, + cancellationTokenProvider: cancellationTokenProvider, + serializer: serializer, + keyNormalizer: keyNormalizer, + serviceScopeFactory: serviceScopeFactory) { } - } + /// /// Represents a distributed cache of type. /// Uses a generic cache key type of type. @@ -148,19 +151,151 @@ namespace Volo.Abp.Caching { if (hideErrors == true) { - AsyncHelper.RunSync(() => HandleExceptionAsync(ex)); + HandleException(ex); return null; } throw; } - if (cachedBytes == null) + return ToCacheItem(cachedBytes); + } + + public virtual KeyValuePair[] GetMany( + IEnumerable keys, + bool? hideErrors = null) + { + var keyArray = keys.ToArray(); + + var cacheSupportsMultipleItems = Cache as ICacheSupportsMultipleItems; + if (cacheSupportsMultipleItems == null) { - return null; + return GetManyFallback( + keyArray, + hideErrors + ); } - return Serializer.Deserialize(cachedBytes); + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + byte[][] cachedBytes; + + try + { + cachedBytes = cacheSupportsMultipleItems.GetMany(keyArray.Select(NormalizeKey)); + } + catch (Exception ex) + { + if (hideErrors == true) + { + HandleException(ex); + return ToCacheItemsWithDefaultValues(keyArray); + } + + throw; + } + + return ToCacheItems(cachedBytes, keyArray); + } + + protected virtual KeyValuePair[] GetManyFallback( + TCacheKey[] keys, + bool? hideErrors = null) + { + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + return keys + .Select(key => new KeyValuePair( + key, + Get(key, hideErrors: false) + ) + ).ToArray(); + } + catch (Exception ex) + { + if (hideErrors == true) + { + HandleException(ex); + return ToCacheItemsWithDefaultValues(keys); + } + + throw; + } + } + + public virtual async Task[]> GetManyAsync( + IEnumerable keys, + bool? hideErrors = null, + CancellationToken token = default) + { + var keyArray = keys.ToArray(); + + var cacheSupportsMultipleItems = Cache as ICacheSupportsMultipleItems; + if (cacheSupportsMultipleItems == null) + { + return await GetManyFallbackAsync( + keyArray, + hideErrors, + token + ); + } + + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + byte[][] cachedBytes; + + try + { + cachedBytes = await cacheSupportsMultipleItems.GetManyAsync( + keyArray.Select(NormalizeKey), + CancellationTokenProvider.FallbackToProvider(token) + ); + } + catch (Exception ex) + { + if (hideErrors == true) + { + await HandleExceptionAsync(ex); + return ToCacheItemsWithDefaultValues(keyArray); + } + + throw; + } + + return ToCacheItems(cachedBytes, keyArray); + } + + protected virtual async Task[]> GetManyFallbackAsync( + TCacheKey[] keys, + bool? hideErrors = null, + CancellationToken token = default) + { + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + var result = new List>(); + + foreach (var key in keys) + { + result.Add(new KeyValuePair( + key, + await GetAsync(key, false, token)) + ); + } + + return result.ToArray(); + } + catch (Exception ex) + { + if (hideErrors == true) + { + await HandleExceptionAsync(ex); + return ToCacheItemsWithDefaultValues(keys); + } + + throw; + } } /// @@ -307,7 +442,7 @@ namespace Volo.Abp.Caching { if (hideErrors == true) { - AsyncHelper.RunSync(() => HandleExceptionAsync(ex)); + HandleException(ex); return; } @@ -354,14 +489,156 @@ namespace Volo.Abp.Caching } } - /// - /// Refreshes the cache value of the given key, and resets its sliding expiration timeout. - /// - /// The key of cached item to be retrieved from the cache. - /// Indicates to throw or hide the exceptions for the distributed cache. + public void SetMany( + IEnumerable> items, + DistributedCacheEntryOptions options = null, + bool? hideErrors = null) + { + var itemsArray = items.ToArray(); + + var cacheSupportsMultipleItems = Cache as ICacheSupportsMultipleItems; + if (cacheSupportsMultipleItems == null) + { + SetManyFallback( + itemsArray, + options, + hideErrors + ); + + return; + } + + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + cacheSupportsMultipleItems.SetMany( + ToRawCacheItems(itemsArray), + options ?? DefaultCacheOptions + ); + } + catch (Exception ex) + { + if (hideErrors == true) + { + HandleException(ex); + return; + } + + throw; + } + } + + protected virtual void SetManyFallback( + KeyValuePair[] items, + DistributedCacheEntryOptions options = null, + bool? hideErrors = null) + { + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + foreach (var item in items) + { + Set( + item.Key, + item.Value, + options: options, + hideErrors: false + ); + } + } + catch (Exception ex) + { + if (hideErrors == true) + { + HandleException(ex); + return; + } + + throw; + } + } + + public virtual async Task SetManyAsync( + IEnumerable> items, + DistributedCacheEntryOptions options = null, + bool? hideErrors = null, + CancellationToken token = default) + { + var itemsArray = items.ToArray(); + + var cacheSupportsMultipleItems = Cache as ICacheSupportsMultipleItems; + if (cacheSupportsMultipleItems == null) + { + await SetManyFallbackAsync( + itemsArray, + options, + hideErrors, + token + ); + + return; + } + + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + await cacheSupportsMultipleItems.SetManyAsync( + ToRawCacheItems(itemsArray), + options ?? DefaultCacheOptions, + CancellationTokenProvider.FallbackToProvider(token) + ); + } + catch (Exception ex) + { + if (hideErrors == true) + { + await HandleExceptionAsync(ex); + return; + } + + throw; + } + } + + protected virtual async Task SetManyFallbackAsync( + KeyValuePair[] items, + DistributedCacheEntryOptions options = null, + bool? hideErrors = null, + CancellationToken token = default) + { + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + foreach (var item in items) + { + await SetAsync( + item.Key, + item.Value, + options: options, + hideErrors: false, + token: token + ); + } + } + catch (Exception ex) + { + if (hideErrors == true) + { + await HandleExceptionAsync(ex); + return; + } + + throw; + } + } + public virtual void Refresh( TCacheKey key, bool? - hideErrors = null) + hideErrors = null) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -373,20 +650,14 @@ namespace Volo.Abp.Caching { if (hideErrors == true) { - AsyncHelper.RunSync(() => HandleExceptionAsync(ex)); + HandleException(ex); return; } throw; } } - /// - /// Refreshes the cache value of the given key, and resets its sliding expiration timeout. - /// - /// The key of cached item 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. + public virtual async Task RefreshAsync( TCacheKey key, bool? hideErrors = null, @@ -410,11 +681,6 @@ namespace Volo.Abp.Caching } } - /// - /// Removes the cache item for given key from cache. - /// - /// The key of cached item to be retrieved from the cache. - /// Indicates to throw or hide the exceptions for the distributed cache. public virtual void Remove( TCacheKey key, bool? hideErrors = null) @@ -429,7 +695,7 @@ namespace Volo.Abp.Caching { if (hideErrors == true) { - AsyncHelper.RunSync(() => HandleExceptionAsync(ex)); + HandleException(ex); return; } @@ -437,13 +703,6 @@ namespace Volo.Abp.Caching } } - /// - /// Removes the cache item for given key from cache. - /// - /// The key of cached item 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. public virtual async Task RemoveAsync( TCacheKey key, bool? hideErrors = null, @@ -467,6 +726,11 @@ namespace Volo.Abp.Caching } } + protected virtual void HandleException(Exception ex) + { + AsyncHelper.RunSync(() => HandleExceptionAsync(ex)); + } + protected virtual async Task HandleExceptionAsync(Exception ex) { Logger.LogException(ex, LogLevel.Warning); @@ -478,5 +742,56 @@ namespace Volo.Abp.Caching .NotifyAsync(new ExceptionNotificationContext(ex, LogLevel.Warning)); } } + + protected virtual KeyValuePair[] ToCacheItems(byte[][] itemBytes, TCacheKey[] itemKeys) + { + if (itemBytes.Length != itemKeys.Length) + { + throw new AbpException("count of the item bytes should be same with the count of the given keys"); + } + + var result = new List>(); + + for (int i = 0; i < itemKeys.Length; i++) + { + result.Add( + new KeyValuePair( + itemKeys[i], + ToCacheItem(itemBytes[i]) + ) + ); + } + + return result.ToArray(); + } + + [CanBeNull] + protected virtual TCacheItem ToCacheItem([CanBeNull] byte[] bytes) + { + if (bytes == null) + { + return null; + } + + return Serializer.Deserialize(bytes); + } + + + protected virtual KeyValuePair[] ToRawCacheItems(KeyValuePair[] items) + { + return items + .Select(i => new KeyValuePair( + NormalizeKey(i.Key), + Serializer.Serialize(i.Value) + ) + ).ToArray(); + } + + private static KeyValuePair[] ToCacheItemsWithDefaultValues(TCacheKey[] keys) + { + return keys + .Select(key => new KeyValuePair(key, default)) + .ToArray(); + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/ICacheSupportsMultipleItems.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/ICacheSupportsMultipleItems.cs new file mode 100644 index 0000000000..af6f87021b --- /dev/null +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/ICacheSupportsMultipleItems.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Distributed; + +namespace Volo.Abp.Caching +{ + public interface ICacheSupportsMultipleItems + { + byte[][] GetMany( + IEnumerable keys + ); + + Task GetManyAsync( + IEnumerable keys, + CancellationToken token = default + ); + + void SetMany( + IEnumerable> items, + DistributedCacheEntryOptions options + ); + + Task SetManyAsync( + IEnumerable> items, + DistributedCacheEntryOptions options, + 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 163da05632..80fe124785 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; @@ -34,6 +35,39 @@ namespace Volo.Abp.Caching TCacheKey key, bool? hideErrors = null ); + + /// + /// Gets multiple cache items with the given keys. + /// + /// The returned list contains exactly the same count of items specified in the given keys. + /// An item in the return list can not be null, but an item in the list has null value + /// if the related key not found in the cache. + /// + /// The keys of cached items to be retrieved from the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. + /// List of cache items. + KeyValuePair[] GetMany( + IEnumerable keys, + bool? hideErrors = null + ); + + /// + /// Gets multiple cache items with the given keys. + /// + /// The returned list contains exactly the same count of items specified in the given keys. + /// An item in the return list can not be null, but an item in the list has null value + /// if the related key not found in the cache. + /// + /// + /// 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. + /// List of cache items. + Task[]> GetManyAsync( + IEnumerable keys, + bool? hideErrors = null, + CancellationToken token = default + ); /// /// Gets a cache item with the given key. If no cache item is found for the given key then returns null. @@ -113,6 +147,35 @@ namespace Volo.Abp.Caching CancellationToken token = default ); + /// + /// Sets multiple cache items. + /// Based on the implementation, this can be more efficient than setting multiple items individually. + /// + /// Items to set on the cache + /// The cache options for the value. + /// Indicates to throw or hide the exceptions for the distributed cache. + void SetMany( + IEnumerable> items, + DistributedCacheEntryOptions options = null, + bool? hideErrors = null + ); + + /// + /// Sets multiple cache items. + /// Based on the implementation, this can be more efficient than setting multiple items individually. + /// + /// Items to set on the cache + /// The cache options for the value. + /// Indicates to throw or hide the exceptions for the distributed cache. + /// The for the task. + /// The indicating that the operation is asynchronous. + Task SetManyAsync( + IEnumerable> items, + DistributedCacheEntryOptions options = null, + bool? hideErrors = null, + CancellationToken token = default + ); + /// /// Refreshes the cache value of the given key, and resets its sliding expiration timeout. /// diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs index 14677a73bd..533362c851 100644 --- a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs +++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Shouldly; using Volo.Abp.Testing; @@ -80,7 +81,6 @@ namespace Volo.Abp.Caching var personCache = GetRequiredService>(); var otherPersonCache = GetRequiredService>(); - var cacheKey = Guid.NewGuid().ToString(); const string personName = "john nash"; @@ -310,5 +310,34 @@ namespace Volo.Abp.Caching cacheItem2.ShouldBeNull(); } + [Fact] + public async Task Should_Set_And_Get_Multiple_Items_Async() + { + var personCache = GetRequiredService>(); + + await personCache.SetManyAsync(new[] + { + new KeyValuePair("john", new PersonCacheItem("John Nash")), + new KeyValuePair("thomas", new PersonCacheItem("Thomas Moore")) + }); + + var cacheItems = await personCache.GetManyAsync(new[] + { + "john", + "thomas", + "baris" //doesn't exist + }); + + cacheItems.Length.ShouldBe(3); + cacheItems[0].Key.ShouldBe("john"); + cacheItems[0].Value.Name.ShouldBe("John Nash"); + cacheItems[1].Key.ShouldBe("thomas"); + cacheItems[1].Value.Name.ShouldBe("Thomas Moore"); + cacheItems[2].Key.ShouldBe("baris"); + cacheItems[2].Value.ShouldBeNull(); + + (await personCache.GetAsync("john")).Name.ShouldBe("John Nash"); + (await personCache.GetAsync("baris")).ShouldBeNull(); + } } } \ No newline at end of file