From f8a251afb9a9549be185279bc5ecad4c100b2467 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Mon, 29 Jun 2020 16:20:24 +0800 Subject: [PATCH 1/4] Storage cache in the unit of work. Resolve #4040 --- .../Volo/Abp/Caching/AbpCachingModule.cs | 2 + .../Volo/Abp/Caching/DistributedCache.cs | 546 ++++++++++++++---- .../Volo/Abp/Caching/IDistributedCache.cs | 34 +- .../Volo/Abp/Caching/UnitOfWorkCacheItem.cs | 43 ++ .../Caching/UnitOfWorkCacheItemExtensions.cs | 11 + .../Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs | 4 +- .../Volo/Abp/Uow/UnitOfWorkExtensions.cs | 45 +- .../Abp/Caching/DistributedCache_Tests.cs | 244 +++++++- .../FeatureManagementStore.cs | 29 +- .../FeatureManagementStore_Tests.cs | 51 ++ .../SettingManagementStore.cs | 31 +- .../SettingManagementStore_Tests.cs | 34 ++ 12 files changed, 924 insertions(+), 150 deletions(-) create mode 100644 framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItem.cs create mode 100644 framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItemExtensions.cs diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs index ba59bf039e..d133ac110b 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs @@ -5,12 +5,14 @@ using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; using Volo.Abp.Serialization; using Volo.Abp.Threading; +using Volo.Abp.Uow; namespace Volo.Abp.Caching { [DependsOn( typeof(AbpThreadingModule), typeof(AbpSerializationModule), + typeof(AbpUnitOfWorkModule), typeof(AbpMultiTenancyModule), typeof(AbpJsonModule))] public class AbpCachingModule : AbpModule 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 5bb1dd2481..28082832b0 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs @@ -14,6 +14,7 @@ using Volo.Abp.DependencyInjection; using Volo.Abp.ExceptionHandling; using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; +using Volo.Abp.Uow; namespace Volo.Abp.Caching { @@ -30,13 +31,15 @@ namespace Volo.Abp.Caching ICancellationTokenProvider cancellationTokenProvider, IDistributedCacheSerializer serializer, IDistributedCacheKeyNormalizer keyNormalizer, - IHybridServiceScopeFactory serviceScopeFactory) : base( - distributedCacheOption: distributedCacheOption, - cache: cache, - cancellationTokenProvider: cancellationTokenProvider, - serializer: serializer, - keyNormalizer: keyNormalizer, - serviceScopeFactory: serviceScopeFactory) + IHybridServiceScopeFactory serviceScopeFactory, + IUnitOfWorkManager unitOfWorkManager) : base( + distributedCacheOption: distributedCacheOption, + cache: cache, + cancellationTokenProvider: cancellationTokenProvider, + serializer: serializer, + keyNormalizer: keyNormalizer, + serviceScopeFactory: serviceScopeFactory, + unitOfWorkManager:unitOfWorkManager) { } } @@ -50,6 +53,8 @@ namespace Volo.Abp.Caching public class DistributedCache : IDistributedCache where TCacheItem : class { + public const string DistributedCacheName = "AbpDistributedCache"; + public ILogger> Logger { get; set; } protected string CacheName { get; set; } @@ -66,6 +71,8 @@ namespace Volo.Abp.Caching protected IHybridServiceScopeFactory ServiceScopeFactory { get; } + protected IUnitOfWorkManager UnitOfWorkManager { get; } + protected SemaphoreSlim SyncSemaphore { get; } protected DistributedCacheEntryOptions DefaultCacheOptions; @@ -78,7 +85,8 @@ namespace Volo.Abp.Caching ICancellationTokenProvider cancellationTokenProvider, IDistributedCacheSerializer serializer, IDistributedCacheKeyNormalizer keyNormalizer, - IHybridServiceScopeFactory serviceScopeFactory) + IHybridServiceScopeFactory serviceScopeFactory, + IUnitOfWorkManager unitOfWorkManager) { _distributedCacheOption = distributedCacheOption.Value; Cache = cache; @@ -87,6 +95,7 @@ namespace Volo.Abp.Caching Serializer = serializer; KeyNormalizer = keyNormalizer; ServiceScopeFactory = serviceScopeFactory; + UnitOfWorkManager = unitOfWorkManager; SyncSemaphore = new SemaphoreSlim(1, 1); @@ -133,14 +142,25 @@ namespace Volo.Abp.Caching /// Gets a cache item with the given key. If no cache item is found for the given key then returns null. /// /// The key of cached item to be retrieved from the 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. /// Indicates to throw or hide the exceptions for the distributed cache. /// The cache item, or null. public virtual TCacheItem Get( TCacheKey key, + bool considerUow = false, bool? hideErrors = null) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + if (ShouldConsiderUow(considerUow)) + { + var value = GetUnitOfWorkCache().GetOrDefault(key)?.GetUnRemovedValueOrNull(); + if (value != null) + { + return value; + } + } + byte[] cachedBytes; try @@ -163,6 +183,7 @@ namespace Volo.Abp.Caching public virtual KeyValuePair[] GetMany( IEnumerable keys, + bool considerUow = false, bool? hideErrors = null) { var keyArray = keys.ToArray(); @@ -172,33 +193,57 @@ namespace Volo.Abp.Caching { return GetManyFallback( keyArray, + considerUow, hideErrors ); } + var cachedValues = new List>(); + var notCachedKeys = new List(); + if (ShouldConsiderUow(considerUow)) + { + var cache = GetUnitOfWorkCache(); + foreach (var key in keyArray) + { + var value = cache.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(keyArray.Select(NormalizeKey)); + cachedBytes = cacheSupportsMultipleItems.GetMany(readKeys.Select(NormalizeKey)); } catch (Exception ex) { if (hideErrors == true) { HandleException(ex); - return ToCacheItemsWithDefaultValues(keyArray); + return ToCacheItemsWithDefaultValues(readKeys); } throw; } - - return ToCacheItems(cachedBytes, keyArray); + + return cachedValues.Concat(ToCacheItems(cachedBytes, readKeys)).ToArray(); } protected virtual KeyValuePair[] GetManyFallback( TCacheKey[] keys, + bool considerUow = false, bool? hideErrors = null) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -208,7 +253,7 @@ namespace Volo.Abp.Caching return keys .Select(key => new KeyValuePair( key, - Get(key, hideErrors: false) + Get(key, considerUow, hideErrors: false) ) ).ToArray(); } @@ -226,6 +271,7 @@ namespace Volo.Abp.Caching public virtual async Task[]> GetManyAsync( IEnumerable keys, + bool considerUow = false, bool? hideErrors = null, CancellationToken token = default) { @@ -236,18 +282,42 @@ namespace Volo.Abp.Caching { return await GetManyFallbackAsync( keyArray, + considerUow, hideErrors, token ); } + var cachedValues = new List>(); + var notCachedKeys = new List(); + if (ShouldConsiderUow(considerUow)) + { + var cache = GetUnitOfWorkCache(); + foreach (var key in keyArray) + { + var value = cache.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( - keyArray.Select(NormalizeKey), + readKeys.Select(NormalizeKey), CancellationTokenProvider.FallbackToProvider(token) ); } @@ -256,17 +326,18 @@ namespace Volo.Abp.Caching if (hideErrors == true) { await HandleExceptionAsync(ex); - return ToCacheItemsWithDefaultValues(keyArray); + return ToCacheItemsWithDefaultValues(readKeys); } throw; } - - return ToCacheItems(cachedBytes, keyArray); + + return cachedValues.Concat(ToCacheItems(cachedBytes, readKeys)).ToArray(); } protected virtual async Task[]> GetManyFallbackAsync( TCacheKey[] keys, + bool considerUow = false, bool? hideErrors = null, CancellationToken token = default) { @@ -280,7 +351,7 @@ namespace Volo.Abp.Caching { result.Add(new KeyValuePair( key, - await GetAsync(key, false, token)) + await GetAsync(key, considerUow, hideErrors: false, token: token)) ); } @@ -302,16 +373,27 @@ namespace Volo.Abp.Caching /// Gets a cache item with the given key. If no cache item is found for the given key then returns null. /// /// The key of cached item to be retrieved from the 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. /// Indicates to throw or hide the exceptions for the distributed cache. /// The for the task. /// The cache item, or null. public virtual async Task GetAsync( TCacheKey key, + bool considerUow = false, bool? hideErrors = null, CancellationToken token = default) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + if (ShouldConsiderUow(considerUow)) + { + var value = GetUnitOfWorkCache().GetOrDefault(key)?.GetUnRemovedValueOrNull(); + if (value != null) + { + return value; + } + } + byte[] cachedBytes; try @@ -347,15 +429,17 @@ namespace Volo.Abp.Caching /// The key of cached item to be retrieved from the cache. /// The factory delegate is used to provide the cache item when no cache item is found for the given . /// The cache options for the factory delegate. + /// 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. /// Indicates to throw or hide the exceptions for the distributed cache. /// The cache item. public virtual TCacheItem GetOrAdd( TCacheKey key, Func factory, Func optionsFactory = null, + bool considerUow = false, bool? hideErrors = null) { - var value = Get(key, hideErrors); + var value = Get(key, considerUow, hideErrors); if (value != null) { return value; @@ -363,14 +447,28 @@ namespace Volo.Abp.Caching using (SyncSemaphore.Lock()) { - value = Get(key, hideErrors); + value = Get(key, considerUow, hideErrors); if (value != null) { return value; } value = factory(); - Set(key, value, optionsFactory?.Invoke(), hideErrors); + + if (ShouldConsiderUow(considerUow)) + { + var uowCache = GetUnitOfWorkCache(); + if (uowCache.TryGetValue(key, out var item)) + { + item.SetValue(value); + } + else + { + uowCache.Add(key, new UnitOfWorkCacheItem(value)); + } + } + + Set(key, value, optionsFactory?.Invoke(), considerUow, hideErrors); } return value; @@ -384,17 +482,19 @@ namespace Volo.Abp.Caching /// The factory delegate is used to provide the cache item when no cache item is 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 item. public virtual async Task GetOrAddAsync( TCacheKey key, Func> factory, Func optionsFactory = null, + bool considerUow = false, bool? hideErrors = null, CancellationToken token = default) { token = CancellationTokenProvider.FallbackToProvider(token); - var value = await GetAsync(key, hideErrors, token); + var value = await GetAsync(key, considerUow, hideErrors, token); if (value != null) { return value; @@ -402,14 +502,28 @@ namespace Volo.Abp.Caching using (await SyncSemaphore.LockAsync(token)) { - value = await GetAsync(key, hideErrors, token); + value = await GetAsync(key, considerUow, hideErrors, token); if (value != null) { return value; } value = await factory(); - await SetAsync(key, value, optionsFactory?.Invoke(), hideErrors, token); + + if (ShouldConsiderUow(considerUow)) + { + var uowCache = GetUnitOfWorkCache(); + if (uowCache.TryGetValue(key, out var item)) + { + item.SetValue(value); + } + else + { + uowCache.Add(key, new UnitOfWorkCacheItem(value)); + } + } + + await SetAsync(key, value, optionsFactory?.Invoke(), considerUow, hideErrors, token); } return value; @@ -421,41 +535,70 @@ namespace Volo.Abp.Caching /// The key of cached item to be retrieved from the cache. /// The cache item value to set in the cache. /// The cache options for the value. + /// 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. /// Indicates to throw or hide the exceptions for the distributed cache. public virtual void Set( TCacheKey key, TCacheItem value, DistributedCacheEntryOptions options = null, + bool considerUow = false, bool? hideErrors = null) { - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - - try + void SetRealCache() { - Cache.Set( - NormalizeKey(key), - Serializer.Serialize(value), - options ?? DefaultCacheOptions - ); + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + Cache.Set( + NormalizeKey(key), + Serializer.Serialize(value), + options ?? DefaultCacheOptions + ); + } + catch (Exception ex) + { + if (hideErrors == true) + { + HandleException(ex); + return; + } + + throw; + } } - catch (Exception ex) + + if (ShouldConsiderUow(considerUow)) { - if (hideErrors == true) + var cache = GetUnitOfWorkCache(); + if (cache.TryGetValue(key, out _)) { - HandleException(ex); - return; + cache[key].SetValue(value); + } + else + { + cache.Add(key, new UnitOfWorkCacheItem(value)); } - throw; + // ReSharper disable once PossibleNullReferenceException + UnitOfWorkManager.Current.OnCompleted(() => + { + SetRealCache(); + return Task.CompletedTask; + }); + } + else + { + SetRealCache(); } } - /// /// Sets the cache item value for the provided key. /// /// The key of cached item to be retrieved from the cache. /// The cache item value to set in the cache. /// The cache options for the value. + /// 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. /// Indicates to throw or hide the exceptions for the distributed cache. /// The for the task. /// The indicating that the operation is asynchronous. @@ -463,35 +606,60 @@ namespace Volo.Abp.Caching TCacheKey key, TCacheItem value, DistributedCacheEntryOptions options = null, + bool considerUow = false, bool? hideErrors = null, CancellationToken token = default) { - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - - try + async Task SetRealCache() { - await Cache.SetAsync( - NormalizeKey(key), - Serializer.Serialize(value), - options ?? DefaultCacheOptions, - CancellationTokenProvider.FallbackToProvider(token) - ); - } - catch (Exception ex) - { - if (hideErrors == true) + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try { - await HandleExceptionAsync(ex); - return; + await Cache.SetAsync( + NormalizeKey(key), + Serializer.Serialize(value), + options ?? DefaultCacheOptions, + CancellationTokenProvider.FallbackToProvider(token) + ); } + catch (Exception ex) + { + if (hideErrors == true) + { + await HandleExceptionAsync(ex); + return; + } - throw; + throw; + } } + + if (ShouldConsiderUow(considerUow)) + { + var cache = GetUnitOfWorkCache(); + if (cache.TryGetValue(key, out _)) + { + cache[key].SetValue(value); + } + else + { + cache.Add(key, new UnitOfWorkCacheItem(value)); + } + + // ReSharper disable once PossibleNullReferenceException + UnitOfWorkManager.Current.OnCompleted(SetRealCache); + } + else + { + await SetRealCache(); + } } public void SetMany( IEnumerable> items, DistributedCacheEntryOptions options = null, + bool considerUow = false, bool? hideErrors = null) { var itemsArray = items.ToArray(); @@ -502,40 +670,73 @@ namespace Volo.Abp.Caching SetManyFallback( itemsArray, options, + considerUow, hideErrors ); - + return; } - - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - try + void SetRealCache() { - cacheSupportsMultipleItems.SetMany( - ToRawCacheItems(itemsArray), - options ?? DefaultCacheOptions - ); + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + cacheSupportsMultipleItems.SetMany( + ToRawCacheItems(itemsArray), + options ?? DefaultCacheOptions + ); + } + catch (Exception ex) + { + if (hideErrors == true) + { + HandleException(ex); + return; + } + + throw; + } } - catch (Exception ex) + + if (ShouldConsiderUow(considerUow)) { - if (hideErrors == true) + var cache = GetUnitOfWorkCache(); + + foreach (var pair in itemsArray) { - HandleException(ex); - return; + if (cache.TryGetValue(pair.Key, out _)) + { + cache[pair.Key].SetValue(pair.Value); + } + else + { + cache.Add(pair.Key, new UnitOfWorkCacheItem(pair.Value)); + } } - throw; + // ReSharper disable once PossibleNullReferenceException + UnitOfWorkManager.Current.OnCompleted(() => + { + SetRealCache(); + return Task.CompletedTask; + }); + } + else + { + SetRealCache(); } } - + protected virtual void SetManyFallback( KeyValuePair[] items, DistributedCacheEntryOptions options = null, + bool considerUow = false, bool? hideErrors = null) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - + try { foreach (var item in items) @@ -543,7 +744,8 @@ namespace Volo.Abp.Caching Set( item.Key, item.Value, - options: options, + options, + considerUow, hideErrors: false ); } @@ -563,6 +765,7 @@ namespace Volo.Abp.Caching public virtual async Task SetManyAsync( IEnumerable> items, DistributedCacheEntryOptions options = null, + bool considerUow = false, bool? hideErrors = null, CancellationToken token = default) { @@ -574,38 +777,67 @@ namespace Volo.Abp.Caching await SetManyFallbackAsync( itemsArray, options, + considerUow, hideErrors, token ); - + return; } - - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - try + async Task SetRealCache() { - await cacheSupportsMultipleItems.SetManyAsync( - ToRawCacheItems(itemsArray), - options ?? DefaultCacheOptions, - CancellationTokenProvider.FallbackToProvider(token) - ); + 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; + } } - catch (Exception ex) + + if (ShouldConsiderUow(considerUow)) { - if (hideErrors == true) + var cache = GetUnitOfWorkCache(); + + foreach (var pair in itemsArray) { - await HandleExceptionAsync(ex); - return; + if (cache.TryGetValue(pair.Key, out _)) + { + cache[pair.Key].SetValue(pair.Value); + } + else + { + cache.Add(pair.Key, new UnitOfWorkCacheItem(pair.Value)); + } } - throw; + // ReSharper disable once PossibleNullReferenceException + UnitOfWorkManager.Current.OnCompleted(SetRealCache); + } + else + { + await SetRealCache(); } } - + protected virtual async Task SetManyFallbackAsync( KeyValuePair[] items, DistributedCacheEntryOptions options = null, + bool considerUow = false, bool? hideErrors = null, CancellationToken token = default) { @@ -618,7 +850,8 @@ namespace Volo.Abp.Caching await SetAsync( item.Key, item.Value, - options: options, + options, + considerUow, hideErrors: false, token: token ); @@ -636,9 +869,14 @@ 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 virtual void Refresh( - TCacheKey key, bool? - hideErrors = null) + TCacheKey key, + bool? hideErrors = null) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -658,6 +896,13 @@ 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. + /// The for the task. + /// The indicating that the operation is asynchronous. public virtual async Task RefreshAsync( TCacheKey key, bool? hideErrors = null, @@ -681,48 +926,106 @@ namespace Volo.Abp.Caching } } + /// + /// Removes the cache item for given key from cache. + /// + /// The key of cached item to be retrieved from the 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. + /// Indicates to throw or hide the exceptions for the distributed cache. public virtual void Remove( TCacheKey key, + bool considerUow = false, bool? hideErrors = null) { - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - - try + void RemoveRealCache() { - Cache.Remove(NormalizeKey(key)); + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + Cache.Remove(NormalizeKey(key)); + } + catch (Exception ex) + { + if (hideErrors == true) + { + HandleException(ex); + return; + } + + throw; + } } - catch (Exception ex) + + if (ShouldConsiderUow(considerUow)) { - if (hideErrors == true) + var cache = GetUnitOfWorkCache(); + if (cache.TryGetValue(key, out _)) { - HandleException(ex); - return; + cache[key].RemoveValue(); } - throw; + // ReSharper disable once PossibleNullReferenceException + UnitOfWorkManager.Current.OnCompleted(() => + { + RemoveRealCache(); + return Task.CompletedTask; + }); + } + else + { + RemoveRealCache(); } } + /// + /// Removes the cache item for given key from cache. + /// + /// The key of cached item to be retrieved from the 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. + /// 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 considerUow = false, bool? hideErrors = null, CancellationToken token = default) { - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - - try + async Task RemoveRealCache() { - await Cache.RemoveAsync(NormalizeKey(key), CancellationTokenProvider.FallbackToProvider(token)); + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + await Cache.RemoveAsync(NormalizeKey(key), CancellationTokenProvider.FallbackToProvider(token)); + } + catch (Exception ex) + { + if (hideErrors == true) + { + await HandleExceptionAsync(ex); + return; + } + + throw; + } } - catch (Exception ex) + + if (ShouldConsiderUow(considerUow)) { - if (hideErrors == true) + var cache = GetUnitOfWorkCache(); + if (cache.TryGetValue(key, out _)) { - await HandleExceptionAsync(ex); - return; + cache[key].RemoveValue(); } - throw; + // ReSharper disable once PossibleNullReferenceException + UnitOfWorkManager.Current.OnCompleted(RemoveRealCache); + } + else + { + await RemoveRealCache(); } } @@ -730,7 +1033,7 @@ namespace Volo.Abp.Caching { AsyncHelper.RunSync(() => HandleExceptionAsync(ex)); } - + protected virtual async Task HandleExceptionAsync(Exception ex) { Logger.LogException(ex, LogLevel.Warning); @@ -742,7 +1045,7 @@ namespace Volo.Abp.Caching .NotifyAsync(new ExceptionNotificationContext(ex, LogLevel.Warning)); } } - + protected virtual KeyValuePair[] ToCacheItems(byte[][] itemBytes, TCacheKey[] itemKeys) { if (itemBytes.Length != itemKeys.Length) @@ -764,7 +1067,7 @@ namespace Volo.Abp.Caching return result.ToArray(); } - + [CanBeNull] protected virtual TCacheItem ToCacheItem([CanBeNull] byte[] bytes) { @@ -786,12 +1089,33 @@ namespace Volo.Abp.Caching ) ).ToArray(); } - + private static KeyValuePair[] ToCacheItemsWithDefaultValues(TCacheKey[] keys) { return keys .Select(key => new KeyValuePair(key, default)) .ToArray(); } + + protected virtual bool ShouldConsiderUow(bool considerUow) + { + return considerUow && UnitOfWorkManager.Current != null; + } + + protected virtual string GetUnitOfWorkCacheKey() + { + return DistributedCacheName + CacheName; + } + + protected virtual Dictionary> GetUnitOfWorkCache() + { + if (UnitOfWorkManager.Current == null) + { + throw new AbpException($"There is no unit of work in the current context, The {GetType().Name} can only be used in a unit of work."); + } + + return UnitOfWorkManager.Current.GetOrAddItem(GetUnitOfWorkCacheKey(), + key => new Dictionary>()); + } } -} \ 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 80fe124785..1b0e58815e 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs @@ -29,42 +29,48 @@ namespace Volo.Abp.Caching /// Gets a cache item with the given key. If no cache item is found for the given key then returns null. /// /// The key of cached item to be retrieved from the 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. /// Indicates to throw or hide the exceptions for the distributed cache. /// The cache item, or null. TCacheItem Get( TCacheKey key, + bool considerUow = false, 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. + /// if the related key not found in the cache. /// /// The keys of cached items to be retrieved from the 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. /// Indicates to throw or hide the exceptions for the distributed cache. /// List of cache items. KeyValuePair[] GetMany( IEnumerable keys, + bool considerUow = false, 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. + /// 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. /// Indicates to throw or hide the exceptions for the distributed cache. /// /// The for the task. /// List of cache items. Task[]> GetManyAsync( IEnumerable keys, + bool considerUow = false, bool? hideErrors = null, CancellationToken token = default ); @@ -74,10 +80,12 @@ namespace Volo.Abp.Caching /// /// The key of cached item to be retrieved from the cache. /// 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 item, or null. Task GetAsync( [NotNull] TCacheKey key, + bool considerUow = false, bool? hideErrors = null, CancellationToken token = default ); @@ -89,12 +97,14 @@ namespace Volo.Abp.Caching /// The key of cached item to be retrieved from the cache. /// The factory delegate is used to provide the cache item when no cache item is found for the given . /// The cache options for the factory delegate. + /// 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. /// Indicates to throw or hide the exceptions for the distributed cache. /// The cache item. TCacheItem GetOrAdd( TCacheKey key, Func factory, Func optionsFactory = null, + bool considerUow = false, bool? hideErrors = null ); @@ -105,6 +115,7 @@ namespace Volo.Abp.Caching /// The key of cached item to be retrieved from the cache. /// The factory delegate is used to provide the cache item when no cache item is found for the given . /// The cache options for the factory delegate. + /// 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. /// Indicates to throw or hide the exceptions for the distributed cache. /// The for the task. /// The cache item. @@ -112,6 +123,7 @@ namespace Volo.Abp.Caching [NotNull] TCacheKey key, Func> factory, Func optionsFactory = null, + bool considerUow = false, bool? hideErrors = null, CancellationToken token = default ); @@ -122,11 +134,13 @@ namespace Volo.Abp.Caching /// The key of cached item to be retrieved from the cache. /// The cache item value to set in the cache. /// The cache options for the value. + /// 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. /// Indicates to throw or hide the exceptions for the distributed cache. void Set( TCacheKey key, TCacheItem value, DistributedCacheEntryOptions options = null, + bool considerUow = false, bool? hideErrors = null ); @@ -136,6 +150,7 @@ namespace Volo.Abp.Caching /// The key of cached item to be retrieved from the cache. /// The cache item value to set in the cache. /// The cache options for the value. + /// 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. /// Indicates to throw or hide the exceptions for the distributed cache. /// The for the task. /// The indicating that the operation is asynchronous. @@ -143,6 +158,7 @@ namespace Volo.Abp.Caching [NotNull] TCacheKey key, [NotNull] TCacheItem value, [CanBeNull] DistributedCacheEntryOptions options = null, + bool considerUow = false, bool? hideErrors = null, CancellationToken token = default ); @@ -153,25 +169,29 @@ namespace Volo.Abp.Caching /// /// Items to set on the cache /// The cache options for the value. + /// 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. /// Indicates to throw or hide the exceptions for the distributed cache. void SetMany( IEnumerable> items, DistributedCacheEntryOptions options = null, + bool considerUow = false, 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. + /// 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. /// 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 considerUow = false, bool? hideErrors = null, CancellationToken token = default ); @@ -203,9 +223,11 @@ namespace Volo.Abp.Caching /// Removes the cache item for given key from cache. /// /// The key of cached item to be retrieved from the 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. /// Indicates to throw or hide the exceptions for the distributed cache. void Remove( TCacheKey key, + bool considerUow = false, bool? hideErrors = null ); @@ -213,11 +235,13 @@ namespace Volo.Abp.Caching /// Removes the cache item for given key from cache. /// /// The key of cached item to be retrieved from the 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. /// Indicates to throw or hide the exceptions for the distributed cache. /// The for the task. /// The indicating that the operation is asynchronous. Task RemoveAsync( TCacheKey key, + bool considerUow = false, bool? hideErrors = null, CancellationToken token = default ); diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItem.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItem.cs new file mode 100644 index 0000000000..c37475d022 --- /dev/null +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItem.cs @@ -0,0 +1,43 @@ +using System; + +namespace Volo.Abp.Caching +{ + [Serializable] + public class UnitOfWorkCacheItem + where TValue : class + { + public bool IsRemoved { get; set; } + + public TValue Value { get; set; } + + public UnitOfWorkCacheItem() + { + + } + + public UnitOfWorkCacheItem(TValue value) + { + Value = value; + } + + public UnitOfWorkCacheItem(TValue value, bool isRemoved) + { + Value = value; + IsRemoved = isRemoved; + } + + public UnitOfWorkCacheItem SetValue(TValue value) + { + Value = value; + IsRemoved = false; + return this; + } + + public UnitOfWorkCacheItem RemoveValue() + { + Value = null; + IsRemoved = true; + return this; + } + } +} diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItemExtensions.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItemExtensions.cs new file mode 100644 index 0000000000..46757f80cf --- /dev/null +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItemExtensions.cs @@ -0,0 +1,11 @@ +namespace Volo.Abp.Caching +{ + public static class UnitOfWorkCacheItemExtensions + { + public static TValue GetUnRemovedValueOrNull(this UnitOfWorkCacheItem item) + where TValue : class + { + return item != null && !item.IsRemoved ? item.Value : null; + } + } +} diff --git a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs index b42b745fb4..b4158229fe 100644 --- a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs +++ b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; @@ -316,4 +316,4 @@ namespace Volo.Abp.Uow return $"[UnitOfWork {Id}]"; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkExtensions.cs b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkExtensions.cs index 5c01f54a99..c183a70390 100644 --- a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkExtensions.cs +++ b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkExtensions.cs @@ -1,4 +1,7 @@ -using JetBrains.Annotations; +using System; +using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; namespace Volo.Abp.Uow { @@ -10,5 +13,43 @@ namespace Volo.Abp.Uow return unitOfWork.IsReserved && unitOfWork.ReservationName == reservationName; } + + public static void AddItem([NotNull] this IUnitOfWork unitOfWork, string key, TValue value) + where TValue : class + { + Check.NotNull(unitOfWork, nameof(unitOfWork)); + + if (!unitOfWork.Items.ContainsKey(key)) + { + unitOfWork.Items[key] = value; + } + else + { + unitOfWork.Items.Add(key, value); + } + } + + public static TValue GetItemOrDefault([NotNull] this IUnitOfWork unitOfWork, string key) + where TValue : class + { + Check.NotNull(unitOfWork, nameof(unitOfWork)); + + return unitOfWork.Items.FirstOrDefault(x => x.Key == key).As(); + } + + public static TValue GetOrAddItem([NotNull] this IUnitOfWork unitOfWork, string key, Func factory) + where TValue : class + { + Check.NotNull(unitOfWork, nameof(unitOfWork)); + + return unitOfWork.Items.GetOrAdd(key, factory).As(); + } + + public static void RemoveItem([NotNull] this IUnitOfWork unitOfWork, string key) + { + Check.NotNull(unitOfWork, nameof(unitOfWork)); + + unitOfWork.Items.RemoveAll(x => x.Key == key); + } } -} \ No newline at end of file +} 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 533362c851..61a61ac2cd 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 @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Shouldly; using Volo.Abp.Testing; +using Volo.Abp.Uow; using Xunit; namespace Volo.Abp.Caching @@ -30,7 +31,7 @@ namespace Volo.Abp.Caching cacheItem.ShouldNotBeNull(); cacheItem.Name.ShouldBe(personName); - //Remove + //Remove await personCache.RemoveAsync(cacheKey); //Get (not exists since removed) @@ -111,7 +112,7 @@ namespace Volo.Abp.Caching cacheItem1.ShouldNotBeNull(); cacheItem1.Name.ShouldBe(personName); - //Remove + //Remove await personCache.RemoveAsync(cacheKey); @@ -145,7 +146,7 @@ namespace Volo.Abp.Caching cacheItem.ShouldNotBeNull(); cacheItem.Name.ShouldBe(personName); - //Remove + //Remove await personCache.RemoveAsync(cacheKey); //Get (not exists since removed) @@ -227,7 +228,7 @@ namespace Volo.Abp.Caching cacheItem1.ShouldNotBeNull(); cacheItem1.Name.ShouldBe(personName); - //Remove + //Remove await personCache.RemoveAsync(cacheKey); @@ -261,7 +262,7 @@ namespace Volo.Abp.Caching cacheItem.ShouldNotBeNull(); cacheItem.Name.ShouldBe(personName); - //Remove + //Remove await personCache.RemoveAsync(cacheKey); //Get (not exists since removed) @@ -299,7 +300,7 @@ namespace Volo.Abp.Caching cacheItem2.ShouldNotBeNull(); cacheItem2.Name.ShouldBe(personName); - //Remove + //Remove await personCache.RemoveAsync(cache1Key); await personCache.RemoveAsync(cache2Key); @@ -310,6 +311,235 @@ namespace Volo.Abp.Caching cacheItem2.ShouldBeNull(); } + [Fact] + public async Task Cache_Should_Only_Available_In_Uow_For_GetAsync() + { + const string key = "testkey"; + + using (var uow = GetRequiredService().Begin()) + { + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldBeNull(); + + await personCache.SetAsync(key, new PersonCacheItem("john"), considerUow: true); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + + uow.OnCompleted(async () => + { + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + }); + + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task Cache_Should_Rollback_With_Uow_For_GetAsync() + { + const string key = "testkey"; + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + + using (var uow = GetRequiredService().Begin()) + { + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldBeNull(); + + await personCache.SetAsync(key, new PersonCacheItem("john"), considerUow: true); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + } + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + } + + [Fact] + public async Task Cache_Should_Only_Available_In_Uow_For_GetOrAddAsync() + { + const string key = "testkey"; + + using (var uow = GetRequiredService().Begin()) + { + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetOrAddAsync(key, () => Task.FromResult(new PersonCacheItem("john")), considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + + uow.OnCompleted(async () => + { + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + }); + + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task Cache_Should_Rollback_With_Uow_For_GetOrAddAsync() + { + const string key = "testkey"; + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + + using (var uow = GetRequiredService().Begin()) + { + cacheValue = await personCache.GetOrAddAsync(key, () => Task.FromResult(new PersonCacheItem("john")), considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + } + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + } + + [Fact] + public async Task Cache_Should_Only_Available_In_Uow_For_SetAsync() + { + const string key = "testkey"; + + using (var uow = GetRequiredService().Begin()) + { + var personCache = GetRequiredService>(); + + await personCache.SetAsync(key, new PersonCacheItem("john"), considerUow: true); + + var cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + + uow.OnCompleted(async () => + { + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + }); + + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task Cache_Should_Rollback_With_Uow_For_SetAsync() + { + const string key = "testkey"; + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + + using (var uow = GetRequiredService().Begin()) + { + await personCache.SetAsync(key, new PersonCacheItem("john"), considerUow: true); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + } + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + } + + [Fact] + public async Task Cache_Should_Only_Available_In_Uow_For_RemoveAsync() + { + const string key = "testkey"; + + using (var uow = GetRequiredService().Begin()) + { + var personCache = GetRequiredService>(); + + await personCache.SetAsync(key, new PersonCacheItem("john"), considerUow: true); + + var cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + await personCache.RemoveAsync(key, considerUow: true); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldBeNull(); + + uow.OnCompleted(async () => + { + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + }); + + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task Cache_Should_Rollback_With_Uow_For_RemoveAsync() + { + const string key = "testkey"; + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + + using (var uow = GetRequiredService().Begin()) + { + await personCache.SetAsync(key, new PersonCacheItem("john"), considerUow: true); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + await personCache.RemoveAsync(key, considerUow: true); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldBeNull(); + } + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + } + [Fact] public async Task Should_Set_And_Get_Multiple_Items_Async() { @@ -340,4 +570,4 @@ namespace Volo.Abp.Caching (await personCache.GetAsync("baris")).ShouldBeNull(); } } -} \ No newline at end of file +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementStore.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementStore.cs index 26388fdaf9..5b892a91bb 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementStore.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementStore.cs @@ -5,6 +5,7 @@ using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; using Volo.Abp.Features; using Volo.Abp.Guids; +using Volo.Abp.Uow; namespace Volo.Abp.FeatureManagement { @@ -27,12 +28,14 @@ namespace Volo.Abp.FeatureManagement FeatureDefinitionManager = featureDefinitionManager; } + [UnitOfWork] public virtual async Task GetOrNullAsync(string name, string providerName, string providerKey) { var cacheItem = await GetCacheItemAsync(name, providerName, providerKey); return cacheItem.Value; } + [UnitOfWork] public virtual async Task SetAsync(string name, string value, string providerName, string providerKey) { var featureValue = await FeatureValueRepository.FindAsync(name, providerName, providerKey); @@ -46,21 +49,25 @@ namespace Volo.Abp.FeatureManagement featureValue.Value = value; await FeatureValueRepository.UpdateAsync(featureValue); } + + await Cache.SetAsync(CalculateCacheKey(name, providerName, providerKey), new FeatureValueCacheItem(featureValue?.Value), considerUow: true); } + [UnitOfWork] public virtual async Task DeleteAsync(string name, string providerName, string providerKey) { var featureValues = await FeatureValueRepository.FindAllAsync(name, providerName, providerKey); foreach (var featureValue in featureValues) { await FeatureValueRepository.DeleteAsync(featureValue); + await Cache.RemoveAsync(CalculateCacheKey(name, providerName, providerKey), considerUow: true); } } protected virtual async Task GetCacheItemAsync(string name, string providerName, string providerKey) { var cacheKey = CalculateCacheKey(name, providerName, providerKey); - var cacheItem = await Cache.GetAsync(cacheKey); + var cacheItem = await Cache.GetAsync(cacheKey, considerUow: true); if (cacheItem != null) { @@ -68,28 +75,28 @@ namespace Volo.Abp.FeatureManagement } cacheItem = new FeatureValueCacheItem(null); - + await SetCacheItemsAsync(providerName, providerKey, name, cacheItem); return cacheItem; } - + private async Task SetCacheItemsAsync( - string providerName, - string providerKey, - string currentName, + string providerName, + string providerKey, + string currentName, FeatureValueCacheItem currentCacheItem) { var featureDefinitions = FeatureDefinitionManager.GetAll(); var featuresDictionary = (await FeatureValueRepository.GetListAsync(providerName, providerKey)) .ToDictionary(s => s.Name, s => s.Value); - - var cacheItems = new List>(); - + + var cacheItems = new List>(); + foreach (var featureDefinition in featureDefinitions) { var featureValue = featuresDictionary.GetOrDefault(featureDefinition.Name); - + cacheItems.Add( new KeyValuePair( CalculateCacheKey(featureDefinition.Name, providerName, providerKey), @@ -103,7 +110,7 @@ namespace Volo.Abp.FeatureManagement } } - await Cache.SetManyAsync(cacheItems); + await Cache.SetManyAsync(cacheItems, considerUow: true); } protected virtual string CalculateCacheKey(string name, string providerName, string providerKey) diff --git a/modules/feature-management/test/Volo.Abp.FeatureManagement.TestBase/Volo/Abp/FeatureManagement/FeatureManagementStore_Tests.cs b/modules/feature-management/test/Volo.Abp.FeatureManagement.TestBase/Volo/Abp/FeatureManagement/FeatureManagementStore_Tests.cs index a6f040dde3..a5250f1804 100644 --- a/modules/feature-management/test/Volo.Abp.FeatureManagement.TestBase/Volo/Abp/FeatureManagement/FeatureManagementStore_Tests.cs +++ b/modules/feature-management/test/Volo.Abp.FeatureManagement.TestBase/Volo/Abp/FeatureManagement/FeatureManagementStore_Tests.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Shouldly; using Volo.Abp.Features; using Volo.Abp.Modularity; +using Volo.Abp.Uow; using Xunit; namespace Volo.Abp.FeatureManagement @@ -14,11 +15,13 @@ namespace Volo.Abp.FeatureManagement { private IFeatureManagementStore FeatureManagementStore { get; set; } private IFeatureValueRepository FeatureValueRepository { get; set; } + private IUnitOfWorkManager UnitOfWorkManager { get; set; } protected FeatureManagementStore_Tests() { FeatureManagementStore = GetRequiredService(); FeatureValueRepository = GetRequiredService(); + UnitOfWorkManager = GetRequiredService(); } [Fact] @@ -73,6 +76,30 @@ namespace Volo.Abp.FeatureManagement TestEditionIds.Regular.ToString())).Value.ShouldBe(false.ToString().ToUpperInvariant()); } + [Fact] + public async Task Set_In_UnitOfWork_Should_Be_Consistent() + { + using (UnitOfWorkManager.Begin()) + { + // Arrange + (await FeatureManagementStore.GetOrNullAsync(TestFeatureDefinitionProvider.SocialLogins, + EditionFeatureValueProvider.ProviderName, + TestEditionIds.Regular.ToString())).ShouldNotBeNull(); + + + // Act + await FeatureManagementStore.SetAsync(TestFeatureDefinitionProvider.SocialLogins, + false.ToString().ToUpperInvariant(), + EditionFeatureValueProvider.ProviderName, + TestEditionIds.Regular.ToString()); + + // Assert + (await FeatureManagementStore.GetOrNullAsync(TestFeatureDefinitionProvider.SocialLogins, + EditionFeatureValueProvider.ProviderName, + TestEditionIds.Regular.ToString())).ShouldBe(false.ToString().ToUpperInvariant()); + } + } + [Fact] public async Task DeleteAsync() { @@ -94,5 +121,29 @@ namespace Volo.Abp.FeatureManagement } + + [Fact] + public async Task Delete_In_UnitOfWork_Should_Be_Consistent() + { + using (var uow = UnitOfWorkManager.Begin()) + { + // Arrange + (await FeatureManagementStore.GetOrNullAsync(TestFeatureDefinitionProvider.SocialLogins, + EditionFeatureValueProvider.ProviderName, + TestEditionIds.Regular.ToString())).ShouldNotBeNull(); + + // Act + await FeatureManagementStore.DeleteAsync(TestFeatureDefinitionProvider.SocialLogins, + EditionFeatureValueProvider.ProviderName, + TestEditionIds.Regular.ToString()); + + await uow.SaveChangesAsync(); + + // Assert + (await FeatureManagementStore.GetOrNullAsync(TestFeatureDefinitionProvider.SocialLogins, + EditionFeatureValueProvider.ProviderName, + TestEditionIds.Regular.ToString())).ShouldBeNull(); + } + } } } diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingManagementStore.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingManagementStore.cs index 617d360898..a568184f0e 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingManagementStore.cs +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingManagementStore.cs @@ -5,6 +5,7 @@ using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; using Volo.Abp.Guids; using Volo.Abp.Settings; +using Volo.Abp.Uow; namespace Volo.Abp.SettingManagement { @@ -16,8 +17,8 @@ namespace Volo.Abp.SettingManagement protected IGuidGenerator GuidGenerator { get; } public SettingManagementStore( - ISettingRepository settingRepository, - IGuidGenerator guidGenerator, + ISettingRepository settingRepository, + IGuidGenerator guidGenerator, IDistributedCache cache, ISettingDefinitionManager settingDefinitionManager) { @@ -27,11 +28,13 @@ namespace Volo.Abp.SettingManagement SettingDefinitionManager = settingDefinitionManager; } + [UnitOfWork] public virtual async Task GetOrNullAsync(string name, string providerName, string providerKey) { return (await GetCacheItemAsync(name, providerName, providerKey)).Value; } + [UnitOfWork] public virtual async Task SetAsync(string name, string value, string providerName, string providerKey) { var setting = await SettingRepository.FindAsync(name, providerName, providerKey); @@ -45,6 +48,8 @@ namespace Volo.Abp.SettingManagement setting.Value = value; await SettingRepository.UpdateAsync(setting); } + + await Cache.SetAsync(CalculateCacheKey(name, providerName, providerKey), new SettingCacheItem(setting?.Value), considerUow: true); } public virtual async Task> GetListAsync(string providerName, string providerKey) @@ -53,19 +58,21 @@ namespace Volo.Abp.SettingManagement return settings.Select(s => new SettingValue(s.Name, s.Value)).ToList(); } + [UnitOfWork] public virtual async Task DeleteAsync(string name, string providerName, string providerKey) { var setting = await SettingRepository.FindAsync(name, providerName, providerKey); if (setting != null) { await SettingRepository.DeleteAsync(setting); + await Cache.RemoveAsync(CalculateCacheKey(name, providerName, providerKey), considerUow: true); } } protected virtual async Task GetCacheItemAsync(string name, string providerName, string providerKey) { var cacheKey = CalculateCacheKey(name, providerName, providerKey); - var cacheItem = await Cache.GetAsync(cacheKey); + var cacheItem = await Cache.GetAsync(cacheKey, considerUow: true); if (cacheItem != null) { @@ -75,26 +82,26 @@ namespace Volo.Abp.SettingManagement cacheItem = new SettingCacheItem(null); await SetCacheItemsAsync(providerName, providerKey, name, cacheItem); - + return cacheItem; } private async Task SetCacheItemsAsync( - string providerName, - string providerKey, - string currentName, + string providerName, + string providerKey, + string currentName, SettingCacheItem currentCacheItem) { var settingDefinitions = SettingDefinitionManager.GetAll(); var settingsDictionary = (await SettingRepository.GetListAsync(providerName, providerKey)) .ToDictionary(s => s.Name, s => s.Value); - - var cacheItems = new List>(); - + + var cacheItems = new List>(); + foreach (var settingDefinition in settingDefinitions) { var settingValue = settingsDictionary.GetOrDefault(settingDefinition.Name); - + cacheItems.Add( new KeyValuePair( CalculateCacheKey(settingDefinition.Name, providerName, providerKey), @@ -108,7 +115,7 @@ namespace Volo.Abp.SettingManagement } } - await Cache.SetManyAsync(cacheItems); + await Cache.SetManyAsync(cacheItems, considerUow: true); } protected virtual string CalculateCacheKey(string name, string providerName, string providerKey) diff --git a/modules/setting-management/test/Volo.Abp.SettingManagement.Tests/Volo/Abp/SettingManagement/SettingManagementStore_Tests.cs b/modules/setting-management/test/Volo.Abp.SettingManagement.Tests/Volo/Abp/SettingManagement/SettingManagementStore_Tests.cs index 753ba15054..7a405faf2d 100644 --- a/modules/setting-management/test/Volo.Abp.SettingManagement.Tests/Volo/Abp/SettingManagement/SettingManagementStore_Tests.cs +++ b/modules/setting-management/test/Volo.Abp.SettingManagement.Tests/Volo/Abp/SettingManagement/SettingManagementStore_Tests.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading.Tasks; using Shouldly; using Volo.Abp.Settings; +using Volo.Abp.Uow; using Xunit; namespace Volo.Abp.SettingManagement @@ -13,12 +14,14 @@ namespace Volo.Abp.SettingManagement private readonly ISettingManagementStore _settingManagementStore; private readonly ISettingRepository _settingRepository; private readonly SettingTestData _testData; + private readonly IUnitOfWorkManager _unitOfWorkManager; public SettingManagementStore_Tests() { _settingManagementStore = GetRequiredService(); _settingRepository = GetRequiredService(); _testData = GetRequiredService(); + _unitOfWorkManager= GetRequiredService(); } [Fact] @@ -50,6 +53,21 @@ namespace Volo.Abp.SettingManagement (await _settingRepository.FindAsync(_testData.SettingId)).Value.ShouldBe("43"); } + [Fact] + public async Task Set_In_UnitOfWork_Should_Be_Consistent() + { + using (_unitOfWorkManager.Begin()) + { + var value = await _settingManagementStore.GetOrNullAsync("MySetting1", GlobalSettingValueProvider.ProviderName, null); + value.ShouldBe("42"); + + await _settingManagementStore.SetAsync("MySetting1", "43", GlobalSettingValueProvider.ProviderName, null); + + var valueAfterSet = await _settingManagementStore.GetOrNullAsync("MySetting1", GlobalSettingValueProvider.ProviderName, null); + valueAfterSet.ShouldBe("43"); + } + } + [Fact] public async Task DeleteAsync() { @@ -60,5 +78,21 @@ namespace Volo.Abp.SettingManagement (await _settingRepository.FindAsync(_testData.SettingId)).ShouldBeNull(); } + [Fact] + public async Task Delete_In_UnitOfWork_Should_Be_Consistent() + { + using (var uow = _unitOfWorkManager.Begin()) + { + (await _settingManagementStore.GetOrNullAsync("MySetting1", GlobalSettingValueProvider.ProviderName, null)).ShouldNotBeNull(); + + await _settingManagementStore.DeleteAsync("MySetting1", GlobalSettingValueProvider.ProviderName, null); + + await uow.SaveChangesAsync(); + + var value = await _settingManagementStore.GetOrNullAsync("MySetting1", GlobalSettingValueProvider.ProviderName, null); + value.ShouldBeNull(); + } + } + } } From c6cdc8d628a2ea87a4473e86cd5bdd85888d5f8d Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Fri, 10 Jul 2020 16:02:54 +0800 Subject: [PATCH 2/4] Add TestMemoryDistributedCache for better test. --- .../Volo/Abp/Caching/AbpCachingTestModule.cs | 6 +- .../Abp/Caching/TestMemoryDistributedCache.cs | 61 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/TestMemoryDistributedCache.cs diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs index be003431f2..9da6725fa5 100644 --- a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs +++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs @@ -1,5 +1,7 @@ using Microsoft.Extensions.Caching.Distributed; using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Volo.Abp.Modularity; namespace Volo.Abp.Caching @@ -26,6 +28,8 @@ namespace Volo.Abp.Caching option.GlobalCacheEntryOptions.SetSlidingExpiration(TimeSpan.FromMinutes(20)); }); + + context.Services.Replace(ServiceDescriptor.Singleton()); } } -} \ No newline at end of file +} 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 new file mode 100644 index 0000000000..69589d97cd --- /dev/null +++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/TestMemoryDistributedCache.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Caching +{ + [DisableConventionalRegistration] + public class TestMemoryDistributedCache : MemoryDistributedCache, ICacheSupportsMultipleItems + { + public TestMemoryDistributedCache(IOptions optionsAccessor) + : base(optionsAccessor) + { + } + + public TestMemoryDistributedCache(IOptions optionsAccessor, ILoggerFactory loggerFactory) + : base(optionsAccessor, loggerFactory) + { + } + + public byte[][] GetMany(IEnumerable keys) + { + var values = new List(); + foreach (var key in keys) + { + values.Add(Get(key)); + } + return values.ToArray(); + } + + public async Task GetManyAsync(IEnumerable keys, CancellationToken token = default) + { + var values = new List(); + foreach (var key in keys) + { + values.Add(await GetAsync(key, token)); + } + return values.ToArray(); + } + + public void SetMany(IEnumerable> items, DistributedCacheEntryOptions options) + { + foreach (var item in items) + { + Set(item.Key, item.Value, options); + } + } + + public async Task SetManyAsync(IEnumerable> items, DistributedCacheEntryOptions options, CancellationToken token = default) + { + foreach (var item in items) + { + await SetAsync(item.Key, item.Value, options, token); + } + } + } +} From a391e7a5280507314e7d22ea7fe1e9b7a1569060 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Mon, 20 Jul 2020 11:07:19 +0800 Subject: [PATCH 3/4] Refactor. --- .../Volo/Abp/Caching/DistributedCache.cs | 160 +++++++++--------- .../Volo/Abp/Caching/IDistributedCache.cs | 58 +++---- .../Abp/Caching/DistributedCache_Tests.cs | 12 +- 3 files changed, 115 insertions(+), 115 deletions(-) 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 28082832b0..b26feec136 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs @@ -53,7 +53,7 @@ namespace Volo.Abp.Caching public class DistributedCache : IDistributedCache where TCacheItem : class { - public const string DistributedCacheName = "AbpDistributedCache"; + public const string UowCacheName = "AbpDistributedCache"; public ILogger> Logger { get; set; } @@ -142,13 +142,13 @@ namespace Volo.Abp.Caching /// Gets a cache item with the given key. If no cache item is found for the given key then returns null. /// /// The key of cached item to be retrieved from the 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. /// 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 item, or null. public virtual TCacheItem Get( TCacheKey key, - bool considerUow = false, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -183,8 +183,8 @@ namespace Volo.Abp.Caching public virtual KeyValuePair[] GetMany( IEnumerable keys, - bool considerUow = false, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { var keyArray = keys.ToArray(); @@ -193,19 +193,19 @@ namespace Volo.Abp.Caching { return GetManyFallback( keyArray, - considerUow, - hideErrors + hideErrors, + considerUow ); } - var cachedValues = new List>(); var notCachedKeys = new List(); + var cachedValues = new List>(); if (ShouldConsiderUow(considerUow)) { - var cache = GetUnitOfWorkCache(); + var uowCache = GetUnitOfWorkCache(); foreach (var key in keyArray) { - var value = cache.GetOrDefault(key)?.GetUnRemovedValueOrNull(); + var value = uowCache.GetOrDefault(key)?.GetUnRemovedValueOrNull(); if (value != null) { cachedValues.Add(new KeyValuePair(key, value)); @@ -232,7 +232,7 @@ namespace Volo.Abp.Caching if (hideErrors == true) { HandleException(ex); - return ToCacheItemsWithDefaultValues(readKeys); + return ToCacheItemsWithDefaultValues(keyArray); } throw; @@ -243,8 +243,8 @@ namespace Volo.Abp.Caching protected virtual KeyValuePair[] GetManyFallback( TCacheKey[] keys, - bool considerUow = false, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -253,7 +253,7 @@ namespace Volo.Abp.Caching return keys .Select(key => new KeyValuePair( key, - Get(key, considerUow, hideErrors: false) + Get(key, false, considerUow) ) ).ToArray(); } @@ -271,8 +271,8 @@ namespace Volo.Abp.Caching public virtual async Task[]> GetManyAsync( IEnumerable keys, - bool considerUow = false, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { var keyArray = keys.ToArray(); @@ -282,20 +282,20 @@ namespace Volo.Abp.Caching { return await GetManyFallbackAsync( keyArray, - considerUow, hideErrors, + considerUow, token ); } - var cachedValues = new List>(); var notCachedKeys = new List(); + var cachedValues = new List>(); if (ShouldConsiderUow(considerUow)) { - var cache = GetUnitOfWorkCache(); + var uowCache = GetUnitOfWorkCache(); foreach (var key in keyArray) { - var value = cache.GetOrDefault(key)?.GetUnRemovedValueOrNull(); + var value = uowCache.GetOrDefault(key)?.GetUnRemovedValueOrNull(); if (value != null) { cachedValues.Add(new KeyValuePair(key, value)); @@ -326,7 +326,7 @@ namespace Volo.Abp.Caching if (hideErrors == true) { await HandleExceptionAsync(ex); - return ToCacheItemsWithDefaultValues(readKeys); + return ToCacheItemsWithDefaultValues(keyArray); } throw; @@ -337,8 +337,8 @@ namespace Volo.Abp.Caching protected virtual async Task[]> GetManyFallbackAsync( TCacheKey[] keys, - bool considerUow = false, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -351,7 +351,7 @@ namespace Volo.Abp.Caching { result.Add(new KeyValuePair( key, - await GetAsync(key, considerUow, hideErrors: false, token: token)) + await GetAsync(key, false, considerUow, token: token)) ); } @@ -373,14 +373,14 @@ namespace Volo.Abp.Caching /// Gets a cache item with the given key. If no cache item is found for the given key then returns null. /// /// The key of cached item to be retrieved from the 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. /// 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 item, or null. public virtual async Task GetAsync( TCacheKey key, - bool considerUow = false, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -429,17 +429,17 @@ namespace Volo.Abp.Caching /// The key of cached item to be retrieved from the cache. /// The factory delegate is used to provide the cache item when no cache item is found for the given . /// The cache options for the factory delegate. - /// 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. /// 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 item. public virtual TCacheItem GetOrAdd( TCacheKey key, Func factory, Func optionsFactory = null, - bool considerUow = false, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { - var value = Get(key, considerUow, hideErrors); + var value = Get(key, hideErrors, considerUow); if (value != null) { return value; @@ -447,7 +447,7 @@ namespace Volo.Abp.Caching using (SyncSemaphore.Lock()) { - value = Get(key, considerUow, hideErrors); + value = Get(key, hideErrors, considerUow); if (value != null) { return value; @@ -468,7 +468,7 @@ namespace Volo.Abp.Caching } } - Set(key, value, optionsFactory?.Invoke(), considerUow, hideErrors); + Set(key, value, optionsFactory?.Invoke(), hideErrors, considerUow); } return value; @@ -489,12 +489,12 @@ namespace Volo.Abp.Caching TCacheKey key, Func> factory, Func optionsFactory = null, - bool considerUow = false, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { token = CancellationTokenProvider.FallbackToProvider(token); - var value = await GetAsync(key, considerUow, hideErrors, token); + var value = await GetAsync(key, hideErrors, considerUow, token); if (value != null) { return value; @@ -502,7 +502,7 @@ namespace Volo.Abp.Caching using (await SyncSemaphore.LockAsync(token)) { - value = await GetAsync(key, considerUow, hideErrors, token); + value = await GetAsync(key, hideErrors, considerUow, token); if (value != null) { return value; @@ -523,7 +523,7 @@ namespace Volo.Abp.Caching } } - await SetAsync(key, value, optionsFactory?.Invoke(), considerUow, hideErrors, token); + await SetAsync(key, value, optionsFactory?.Invoke(), hideErrors, considerUow, token); } return value; @@ -535,14 +535,14 @@ namespace Volo.Abp.Caching /// The key of cached item to be retrieved from the cache. /// The cache item value to set in the cache. /// The cache options for the value. - /// 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. /// 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. public virtual void Set( TCacheKey key, TCacheItem value, DistributedCacheEntryOptions options = null, - bool considerUow = false, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { void SetRealCache() { @@ -570,14 +570,14 @@ namespace Volo.Abp.Caching if (ShouldConsiderUow(considerUow)) { - var cache = GetUnitOfWorkCache(); - if (cache.TryGetValue(key, out _)) + var uowCache = GetUnitOfWorkCache(); + if (uowCache.TryGetValue(key, out _)) { - cache[key].SetValue(value); + uowCache[key].SetValue(value); } else { - cache.Add(key, new UnitOfWorkCacheItem(value)); + uowCache.Add(key, new UnitOfWorkCacheItem(value)); } // ReSharper disable once PossibleNullReferenceException @@ -598,16 +598,16 @@ namespace Volo.Abp.Caching /// The key of cached item to be retrieved from the cache. /// The cache item value to set in the cache. /// The cache options for the value. - /// 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. /// 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 indicating that the operation is asynchronous. public virtual async Task SetAsync( TCacheKey key, TCacheItem value, DistributedCacheEntryOptions options = null, - bool considerUow = false, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { async Task SetRealCache() @@ -637,14 +637,14 @@ namespace Volo.Abp.Caching if (ShouldConsiderUow(considerUow)) { - var cache = GetUnitOfWorkCache(); - if (cache.TryGetValue(key, out _)) + var uowCache = GetUnitOfWorkCache(); + if (uowCache.TryGetValue(key, out _)) { - cache[key].SetValue(value); + uowCache[key].SetValue(value); } else { - cache.Add(key, new UnitOfWorkCacheItem(value)); + uowCache.Add(key, new UnitOfWorkCacheItem(value)); } // ReSharper disable once PossibleNullReferenceException @@ -659,8 +659,8 @@ namespace Volo.Abp.Caching public void SetMany( IEnumerable> items, DistributedCacheEntryOptions options = null, - bool considerUow = false, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { var itemsArray = items.ToArray(); @@ -670,8 +670,8 @@ namespace Volo.Abp.Caching SetManyFallback( itemsArray, options, - considerUow, - hideErrors + hideErrors, + considerUow ); return; @@ -702,17 +702,17 @@ namespace Volo.Abp.Caching if (ShouldConsiderUow(considerUow)) { - var cache = GetUnitOfWorkCache(); + var uowCache = GetUnitOfWorkCache(); foreach (var pair in itemsArray) { - if (cache.TryGetValue(pair.Key, out _)) + if (uowCache.TryGetValue(pair.Key, out _)) { - cache[pair.Key].SetValue(pair.Value); + uowCache[pair.Key].SetValue(pair.Value); } else { - cache.Add(pair.Key, new UnitOfWorkCacheItem(pair.Value)); + uowCache.Add(pair.Key, new UnitOfWorkCacheItem(pair.Value)); } } @@ -732,8 +732,8 @@ namespace Volo.Abp.Caching protected virtual void SetManyFallback( KeyValuePair[] items, DistributedCacheEntryOptions options = null, - bool considerUow = false, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -745,8 +745,8 @@ namespace Volo.Abp.Caching item.Key, item.Value, options, - considerUow, - hideErrors: false + false, + considerUow ); } } @@ -765,8 +765,8 @@ namespace Volo.Abp.Caching public virtual async Task SetManyAsync( IEnumerable> items, DistributedCacheEntryOptions options = null, - bool considerUow = false, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { var itemsArray = items.ToArray(); @@ -777,8 +777,8 @@ namespace Volo.Abp.Caching await SetManyFallbackAsync( itemsArray, options, - considerUow, hideErrors, + considerUow, token ); @@ -811,17 +811,17 @@ namespace Volo.Abp.Caching if (ShouldConsiderUow(considerUow)) { - var cache = GetUnitOfWorkCache(); + var uowCache = GetUnitOfWorkCache(); foreach (var pair in itemsArray) { - if (cache.TryGetValue(pair.Key, out _)) + if (uowCache.TryGetValue(pair.Key, out _)) { - cache[pair.Key].SetValue(pair.Value); + uowCache[pair.Key].SetValue(pair.Value); } else { - cache.Add(pair.Key, new UnitOfWorkCacheItem(pair.Value)); + uowCache.Add(pair.Key, new UnitOfWorkCacheItem(pair.Value)); } } @@ -837,8 +837,8 @@ namespace Volo.Abp.Caching protected virtual async Task SetManyFallbackAsync( KeyValuePair[] items, DistributedCacheEntryOptions options = null, - bool considerUow = false, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -851,8 +851,8 @@ namespace Volo.Abp.Caching item.Key, item.Value, options, + false, considerUow, - hideErrors: false, token: token ); } @@ -934,8 +934,8 @@ namespace Volo.Abp.Caching /// Indicates to throw or hide the exceptions for the distributed cache. public virtual void Remove( TCacheKey key, - bool considerUow = false, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { void RemoveRealCache() { @@ -959,10 +959,10 @@ namespace Volo.Abp.Caching if (ShouldConsiderUow(considerUow)) { - var cache = GetUnitOfWorkCache(); - if (cache.TryGetValue(key, out _)) + var uowCache = GetUnitOfWorkCache(); + if (uowCache.TryGetValue(key, out _)) { - cache[key].RemoveValue(); + uowCache[key].RemoveValue(); } // ReSharper disable once PossibleNullReferenceException @@ -982,14 +982,14 @@ namespace Volo.Abp.Caching /// Removes the cache item for given key from cache. /// /// The key of cached item to be retrieved from the 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. /// 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 indicating that the operation is asynchronous. public virtual async Task RemoveAsync( TCacheKey key, - bool considerUow = false, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { async Task RemoveRealCache() @@ -1014,10 +1014,10 @@ namespace Volo.Abp.Caching if (ShouldConsiderUow(considerUow)) { - var cache = GetUnitOfWorkCache(); - if (cache.TryGetValue(key, out _)) + var uowCache = GetUnitOfWorkCache(); + if (uowCache.TryGetValue(key, out _)) { - cache[key].RemoveValue(); + uowCache[key].RemoveValue(); } // ReSharper disable once PossibleNullReferenceException @@ -1104,14 +1104,14 @@ namespace Volo.Abp.Caching protected virtual string GetUnitOfWorkCacheKey() { - return DistributedCacheName + CacheName; + return UowCacheName + CacheName; } protected virtual Dictionary> GetUnitOfWorkCache() { if (UnitOfWorkManager.Current == null) { - throw new AbpException($"There is no unit of work in the current context, The {GetType().Name} can only be used in a unit of work."); + throw new AbpException($"There is no active UOW."); } return UnitOfWorkManager.Current.GetOrAddItem(GetUnitOfWorkCacheKey(), 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 1b0e58815e..2f74fd678b 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs @@ -29,13 +29,13 @@ namespace Volo.Abp.Caching /// Gets a cache item with the given key. If no cache item is found for the given key then returns null. /// /// The key of cached item to be retrieved from the 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. /// 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 item, or null. TCacheItem Get( TCacheKey key, - bool considerUow = false, - bool? hideErrors = null + bool? hideErrors = null, + bool considerUow = false ); /// @@ -46,13 +46,13 @@ namespace Volo.Abp.Caching /// if the related key not found in the cache. /// /// The keys of cached items to be retrieved from the 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. /// 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. /// List of cache items. KeyValuePair[] GetMany( IEnumerable keys, - bool considerUow = false, - bool? hideErrors = null + bool? hideErrors = null, + bool considerUow = false ); /// @@ -64,14 +64,14 @@ namespace Volo.Abp.Caching /// /// /// The keys of cached items to be retrieved from the 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. /// 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. /// List of cache items. Task[]> GetManyAsync( IEnumerable keys, - bool considerUow = false, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default ); @@ -85,8 +85,8 @@ namespace Volo.Abp.Caching /// The cache item, or null. Task GetAsync( [NotNull] TCacheKey key, - bool considerUow = false, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default ); @@ -97,15 +97,15 @@ namespace Volo.Abp.Caching /// The key of cached item to be retrieved from the cache. /// The factory delegate is used to provide the cache item when no cache item is found for the given . /// The cache options for the factory delegate. - /// 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. /// 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 item. TCacheItem GetOrAdd( TCacheKey key, Func factory, Func optionsFactory = null, - bool considerUow = false, - bool? hideErrors = null + bool? hideErrors = null, + bool considerUow = false ); /// @@ -115,16 +115,16 @@ namespace Volo.Abp.Caching /// The key of cached item to be retrieved from the cache. /// The factory delegate is used to provide the cache item when no cache item is found for the given . /// The cache options for the factory delegate. - /// 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. /// 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 item. Task GetOrAddAsync( [NotNull] TCacheKey key, Func> factory, Func optionsFactory = null, - bool considerUow = false, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default ); @@ -134,14 +134,14 @@ namespace Volo.Abp.Caching /// The key of cached item to be retrieved from the cache. /// The cache item value to set in the cache. /// The cache options for the value. - /// 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. /// 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. void Set( TCacheKey key, TCacheItem value, DistributedCacheEntryOptions options = null, - bool considerUow = false, - bool? hideErrors = null + bool? hideErrors = null, + bool considerUow = false ); /// @@ -150,16 +150,16 @@ namespace Volo.Abp.Caching /// The key of cached item to be retrieved from the cache. /// The cache item value to set in the cache. /// The cache options for the value. - /// 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. /// 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 indicating that the operation is asynchronous. Task SetAsync( [NotNull] TCacheKey key, [NotNull] TCacheItem value, [CanBeNull] DistributedCacheEntryOptions options = null, - bool considerUow = false, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default ); @@ -169,13 +169,13 @@ namespace Volo.Abp.Caching /// /// Items to set on the cache /// The cache options for the value. - /// 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. /// 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. void SetMany( IEnumerable> items, DistributedCacheEntryOptions options = null, - bool considerUow = false, - bool? hideErrors = null + bool? hideErrors = null, + bool considerUow = false ); /// @@ -184,15 +184,15 @@ namespace Volo.Abp.Caching /// /// Items to set on the cache /// The cache options for the value. - /// 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. /// 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 indicating that the operation is asynchronous. Task SetManyAsync( IEnumerable> items, DistributedCacheEntryOptions options = null, - bool considerUow = false, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default ); @@ -223,26 +223,26 @@ namespace Volo.Abp.Caching /// Removes the cache item for given key from cache. /// /// The key of cached item to be retrieved from the 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. /// 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. void Remove( TCacheKey key, - bool considerUow = false, - bool? hideErrors = null + bool? hideErrors = null, + bool considerUow = false ); /// /// Removes the cache item for given key from cache. /// /// The key of cached item to be retrieved from the 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. /// 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 indicating that the operation is asynchronous. Task RemoveAsync( TCacheKey key, - bool considerUow = false, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default ); } 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 61a61ac2cd..35a72362ac 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 @@ -181,10 +181,10 @@ namespace Volo.Abp.Caching factoryExecuted = false; cacheItem = await personCache.GetOrAddAsync(cacheKey, - async () => + () => { factoryExecuted = true; - return new PersonCacheItem(personName); + return Task.FromResult(new PersonCacheItem(personName)); }); factoryExecuted.ShouldBeFalse(); @@ -539,7 +539,7 @@ namespace Volo.Abp.Caching cacheValue = await personCache.GetAsync(key, considerUow: false); cacheValue.ShouldBeNull(); } - + [Fact] public async Task Should_Set_And_Get_Multiple_Items_Async() { @@ -547,7 +547,7 @@ namespace Volo.Abp.Caching await personCache.SetManyAsync(new[] { - new KeyValuePair("john", new PersonCacheItem("John Nash")), + new KeyValuePair("john", new PersonCacheItem("John Nash")), new KeyValuePair("thomas", new PersonCacheItem("Thomas Moore")) }); @@ -557,7 +557,7 @@ namespace Volo.Abp.Caching "thomas", "baris" //doesn't exist }); - + cacheItems.Length.ShouldBe(3); cacheItems[0].Key.ShouldBe("john"); cacheItems[0].Value.Name.ShouldBe("John Nash"); @@ -565,7 +565,7 @@ namespace Volo.Abp.Caching 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(); } From 57fdecc462a44fea4e64717956ebb8b93b87bb3a Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Mon, 20 Jul 2020 13:00:06 +0800 Subject: [PATCH 4/4] Add GetMany & SetMany unit tests. --- .../Abp/Caching/DistributedCache_Tests.cs | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) 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 35a72362ac..32876c08fe 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,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Shouldly; using Volo.Abp.Testing; @@ -540,6 +541,148 @@ namespace Volo.Abp.Caching cacheValue.ShouldBeNull(); } + [Fact] + public async Task Cache_Should_Only_Available_In_Uow_For_GetManyAsync() + { + var testkey = "testkey"; + var testkey2 = "testkey2"; + var testKeys = new[] {testkey, testkey2}; + + using (var uow = GetRequiredService().Begin()) + { + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetManyAsync(testKeys, considerUow: true); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + + await personCache.SetManyAsync(new List> + { + new KeyValuePair(testkey, new PersonCacheItem("john")), + new KeyValuePair(testkey2, new PersonCacheItem("jack")) + }, considerUow: true); + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: true); + cacheValue.Where(x => x.Value != null).ShouldNotBeEmpty(); + cacheValue.ShouldContain(x => x.Value.Name == "john" || x.Value.Name == "jack"); + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + + uow.OnCompleted(async () => + { + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.ShouldNotBeEmpty(); + cacheValue.ShouldContain(x => x.Value.Name == "john" || x.Value.Name == "jack"); + }); + + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task Cache_Should_Rollback_With_Uow_For_GetManyAsync() + { + var testkey = "testkey"; + var testkey2 = "testkey2"; + var testKeys = new[] {testkey, testkey2}; + + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + + using (var uow = GetRequiredService().Begin()) + { + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: true); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + + await personCache.SetManyAsync(new List> + { + new KeyValuePair(testkey, new PersonCacheItem("john")), + new KeyValuePair(testkey2, new PersonCacheItem("jack")) + }, considerUow: true); + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: true); + cacheValue.Where(x => x.Value != null).ShouldNotBeEmpty(); + cacheValue.ShouldContain(x => x.Value.Name == "john" || x.Value.Name == "jack"); + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + } + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + } + + + [Fact] + public async Task Cache_Should_Only_Available_In_Uow_For_SetManyAsync() + { + var testkey = "testkey"; + var testkey2 = "testkey2"; + var testKeys = new[] {testkey, testkey2}; + + using (var uow = GetRequiredService().Begin()) + { + var personCache = GetRequiredService>(); + + await personCache.SetManyAsync(new List> + { + new KeyValuePair(testkey, new PersonCacheItem("john")), + new KeyValuePair(testkey, new PersonCacheItem("jack")) + }, considerUow: true); + + var cacheValue = await personCache.GetManyAsync(testKeys, considerUow: true); + cacheValue.Where(x => x.Value != null).ShouldNotBeEmpty(); + cacheValue.ShouldContain(x => x.Value.Name == "john" || x.Value.Name == "jack"); + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + + uow.OnCompleted(async () => + { + var cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldNotBeEmpty(); + cacheValue.ShouldContain(x => x.Value.Name == "john" || x.Value.Name == "jack"); + }); + + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task Cache_Should_Rollback_With_Uow_For_SetManyAsync() + { + var testkey = "testkey"; + var testkey2 = "testkey2"; + var testKeys = new[] {testkey, testkey2}; + + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + + using (var uow = GetRequiredService().Begin()) + { + await personCache.SetManyAsync(new List> + { + new KeyValuePair(testkey, new PersonCacheItem("john")), + new KeyValuePair(testkey, new PersonCacheItem("jack")) + }, considerUow: true); + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: true); + cacheValue.Where(x => x.Value != null).ShouldNotBeEmpty(); + cacheValue.ShouldContain(x => x.Value.Name == "john" || x.Value.Name == "jack"); + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + } + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + } + + [Fact] public async Task Should_Set_And_Get_Multiple_Items_Async() {