Storage cache in the unit of work.

Resolve #4040
pull/4523/head
maliming 5 years ago
parent e07fb5ebf1
commit f8a251afb9

@ -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

@ -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<TCacheItem, TCacheKey> : IDistributedCache<TCacheItem, TCacheKey>
where TCacheItem : class
{
public const string DistributedCacheName = "AbpDistributedCache";
public ILogger<DistributedCache<TCacheItem, TCacheKey>> 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.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="considerUow">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.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <returns>The cache item, or null.</returns>
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<TCacheKey, TCacheItem>[] GetMany(
IEnumerable<TCacheKey> 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<KeyValuePair<TCacheKey, TCacheItem>>();
var notCachedKeys = new List<TCacheKey>();
if (ShouldConsiderUow(considerUow))
{
var cache = GetUnitOfWorkCache();
foreach (var key in keyArray)
{
var value = cache.GetOrDefault(key)?.GetUnRemovedValueOrNull();
if (value != null)
{
cachedValues.Add(new KeyValuePair<TCacheKey, TCacheItem>(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<TCacheKey, TCacheItem>[] 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<TCacheKey, TCacheItem>(
key,
Get(key, hideErrors: false)
Get(key, considerUow, hideErrors: false)
)
).ToArray();
}
@ -226,6 +271,7 @@ namespace Volo.Abp.Caching
public virtual async Task<KeyValuePair<TCacheKey, TCacheItem>[]> GetManyAsync(
IEnumerable<TCacheKey> 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<KeyValuePair<TCacheKey, TCacheItem>>();
var notCachedKeys = new List<TCacheKey>();
if (ShouldConsiderUow(considerUow))
{
var cache = GetUnitOfWorkCache();
foreach (var key in keyArray)
{
var value = cache.GetOrDefault(key)?.GetUnRemovedValueOrNull();
if (value != null)
{
cachedValues.Add(new KeyValuePair<TCacheKey, TCacheItem>(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<KeyValuePair<TCacheKey, TCacheItem>[]> GetManyFallbackAsync(
TCacheKey[] keys,
bool considerUow = false,
bool? hideErrors = null,
CancellationToken token = default)
{
@ -280,7 +351,7 @@ namespace Volo.Abp.Caching
{
result.Add(new KeyValuePair<TCacheKey, TCacheItem>(
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.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="considerUow">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.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The cache item, or null.</returns>
public virtual async Task<TCacheItem> 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
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="factory">The factory delegate is used to provide the cache item when no cache item is found for the given <paramref name="key" />.</param>
/// <param name="optionsFactory">The cache options for the factory delegate.</param>
/// <param name="considerUow">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.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <returns>The cache item.</returns>
public virtual TCacheItem GetOrAdd(
TCacheKey key,
Func<TCacheItem> factory,
Func<DistributedCacheEntryOptions> 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<TCacheItem>(value));
}
}
Set(key, value, optionsFactory?.Invoke(), considerUow, hideErrors);
}
return value;
@ -384,17 +482,19 @@ namespace Volo.Abp.Caching
/// <param name="factory">The factory delegate is used to provide the cache item when no cache item is found for the given <paramref name="key" />.</param>
/// <param name="optionsFactory">The cache options for the factory delegate.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="considerUow">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.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The cache item.</returns>
public virtual async Task<TCacheItem> GetOrAddAsync(
TCacheKey key,
Func<Task<TCacheItem>> factory,
Func<DistributedCacheEntryOptions> 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<TCacheItem>(value));
}
}
await SetAsync(key, value, optionsFactory?.Invoke(), considerUow, hideErrors, token);
}
return value;
@ -421,41 +535,70 @@ namespace Volo.Abp.Caching
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="value">The cache item value to set in the cache.</param>
/// <param name="options">The cache options for the value.</param>
/// <param name="considerUow">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.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
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<TCacheItem>(value));
}
throw;
// ReSharper disable once PossibleNullReferenceException
UnitOfWorkManager.Current.OnCompleted(() =>
{
SetRealCache();
return Task.CompletedTask;
});
}
else
{
SetRealCache();
}
}
/// <summary>
/// Sets the cache item value for the provided key.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="value">The cache item value to set in the cache.</param>
/// <param name="options">The cache options for the value.</param>
/// <param name="considerUow">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.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
@ -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<TCacheItem>(value));
}
// ReSharper disable once PossibleNullReferenceException
UnitOfWorkManager.Current.OnCompleted(SetRealCache);
}
else
{
await SetRealCache();
}
}
public void SetMany(
IEnumerable<KeyValuePair<TCacheKey, TCacheItem>> 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<TCacheItem>(pair.Value));
}
}
throw;
// ReSharper disable once PossibleNullReferenceException
UnitOfWorkManager.Current.OnCompleted(() =>
{
SetRealCache();
return Task.CompletedTask;
});
}
else
{
SetRealCache();
}
}
protected virtual void SetManyFallback(
KeyValuePair<TCacheKey, TCacheItem>[] 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<KeyValuePair<TCacheKey, TCacheItem>> 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<TCacheItem>(pair.Value));
}
}
throw;
// ReSharper disable once PossibleNullReferenceException
UnitOfWorkManager.Current.OnCompleted(SetRealCache);
}
else
{
await SetRealCache();
}
}
protected virtual async Task SetManyFallbackAsync(
KeyValuePair<TCacheKey, TCacheItem>[] 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
}
}
/// <summary>
/// Refreshes the cache value of the given key, and resets its sliding expiration timeout.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
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
}
}
/// <summary>
/// Refreshes the cache value of the given key, and resets its sliding expiration timeout.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
public virtual async Task RefreshAsync(
TCacheKey key,
bool? hideErrors = null,
@ -681,48 +926,106 @@ namespace Volo.Abp.Caching
}
}
/// <summary>
/// Removes the cache item for given key from cache.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="considerUow">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.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
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();
}
}
/// <summary>
/// Removes the cache item for given key from cache.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="considerUow">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.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
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<TCacheKey, TCacheItem>[] 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<TCacheKey, TCacheItem>[] ToCacheItemsWithDefaultValues(TCacheKey[] keys)
{
return keys
.Select(key => new KeyValuePair<TCacheKey, TCacheItem>(key, default))
.ToArray();
}
protected virtual bool ShouldConsiderUow(bool considerUow)
{
return considerUow && UnitOfWorkManager.Current != null;
}
protected virtual string GetUnitOfWorkCacheKey()
{
return DistributedCacheName + CacheName;
}
protected virtual Dictionary<TCacheKey, UnitOfWorkCacheItem<TCacheItem>> 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<TCacheKey, UnitOfWorkCacheItem<TCacheItem>>());
}
}
}
}

@ -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.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="considerUow">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.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <returns>The cache item, or null.</returns>
TCacheItem Get(
TCacheKey key,
bool considerUow = false,
bool? hideErrors = null
);
/// <summary>
/// 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.
/// </summary>
/// <param name="keys">The keys of cached items to be retrieved from the cache.</param>
/// <param name="considerUow">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.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <returns>List of cache items.</returns>
KeyValuePair<TCacheKey, TCacheItem>[] GetMany(
IEnumerable<TCacheKey> keys,
bool considerUow = false,
bool? hideErrors = null
);
/// <summary>
/// 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.
///
///
/// </summary>
/// <param name="keys">The keys of cached items to be retrieved from the cache.</param>
/// <param name="considerUow">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.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// /// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>List of cache items.</returns>
Task<KeyValuePair<TCacheKey, TCacheItem>[]> GetManyAsync(
IEnumerable<TCacheKey> keys,
bool considerUow = false,
bool? hideErrors = null,
CancellationToken token = default
);
@ -74,10 +80,12 @@ namespace Volo.Abp.Caching
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="considerUow">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.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The cache item, or null.</returns>
Task<TCacheItem> GetAsync(
[NotNull] TCacheKey key,
bool considerUow = false,
bool? hideErrors = null,
CancellationToken token = default
);
@ -89,12 +97,14 @@ namespace Volo.Abp.Caching
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="factory">The factory delegate is used to provide the cache item when no cache item is found for the given <paramref name="key" />.</param>
/// <param name="optionsFactory">The cache options for the factory delegate.</param>
/// <param name="considerUow">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.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <returns>The cache item.</returns>
TCacheItem GetOrAdd(
TCacheKey key,
Func<TCacheItem> factory,
Func<DistributedCacheEntryOptions> optionsFactory = null,
bool considerUow = false,
bool? hideErrors = null
);
@ -105,6 +115,7 @@ namespace Volo.Abp.Caching
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="factory">The factory delegate is used to provide the cache item when no cache item is found for the given <paramref name="key" />.</param>
/// <param name="optionsFactory">The cache options for the factory delegate.</param>
/// <param name="considerUow">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.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The cache item.</returns>
@ -112,6 +123,7 @@ namespace Volo.Abp.Caching
[NotNull] TCacheKey key,
Func<Task<TCacheItem>> factory,
Func<DistributedCacheEntryOptions> optionsFactory = null,
bool considerUow = false,
bool? hideErrors = null,
CancellationToken token = default
);
@ -122,11 +134,13 @@ namespace Volo.Abp.Caching
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="value">The cache item value to set in the cache.</param>
/// <param name="options">The cache options for the value.</param>
/// <param name="considerUow">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.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
void Set(
TCacheKey key,
TCacheItem value,
DistributedCacheEntryOptions options = null,
bool considerUow = false,
bool? hideErrors = null
);
@ -136,6 +150,7 @@ namespace Volo.Abp.Caching
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="value">The cache item value to set in the cache.</param>
/// <param name="options">The cache options for the value.</param>
/// <param name="considerUow">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.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
@ -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
/// </summary>
/// <param name="items">Items to set on the cache</param>
/// <param name="options">The cache options for the value.</param>
/// <param name="considerUow">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.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
void SetMany(
IEnumerable<KeyValuePair<TCacheKey, TCacheItem>> items,
DistributedCacheEntryOptions options = null,
bool considerUow = false,
bool? hideErrors = null
);
/// <summary>
/// Sets multiple cache items.
/// Based on the implementation, this can be more efficient than setting multiple items individually.
/// </summary>
/// <param name="items">Items to set on the cache</param>
/// <param name="options">The cache options for the value.</param>
/// <param name="considerUow">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.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
Task SetManyAsync(
IEnumerable<KeyValuePair<TCacheKey, TCacheItem>> 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.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="considerUow">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.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
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.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="considerUow">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.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
Task RemoveAsync(
TCacheKey key,
bool considerUow = false,
bool? hideErrors = null,
CancellationToken token = default
);

@ -0,0 +1,43 @@
using System;
namespace Volo.Abp.Caching
{
[Serializable]
public class UnitOfWorkCacheItem<TValue>
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<TValue> SetValue(TValue value)
{
Value = value;
IsRemoved = false;
return this;
}
public UnitOfWorkCacheItem<TValue> RemoveValue()
{
Value = null;
IsRemoved = true;
return this;
}
}
}

@ -0,0 +1,11 @@
namespace Volo.Abp.Caching
{
public static class UnitOfWorkCacheItemExtensions
{
public static TValue GetUnRemovedValueOrNull<TValue>(this UnitOfWorkCacheItem<TValue> item)
where TValue : class
{
return item != null && !item.IsRemoved ? item.Value : null;
}
}
}

@ -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}]";
}
}
}
}

@ -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<TValue>([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<TValue>([NotNull] this IUnitOfWork unitOfWork, string key)
where TValue : class
{
Check.NotNull(unitOfWork, nameof(unitOfWork));
return unitOfWork.Items.FirstOrDefault(x => x.Key == key).As<TValue>();
}
public static TValue GetOrAddItem<TValue>([NotNull] this IUnitOfWork unitOfWork, string key, Func<string, TValue> factory)
where TValue : class
{
Check.NotNull(unitOfWork, nameof(unitOfWork));
return unitOfWork.Items.GetOrAdd(key, factory).As<TValue>();
}
public static void RemoveItem([NotNull] this IUnitOfWork unitOfWork, string key)
{
Check.NotNull(unitOfWork, nameof(unitOfWork));
unitOfWork.Items.RemoveAll(x => x.Key == key);
}
}
}
}

@ -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<IUnitOfWorkManager>().Begin())
{
var personCache = GetRequiredService<IDistributedCache<PersonCacheItem>>();
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<IDistributedCache<PersonCacheItem>>();
var cacheValue = await personCache.GetAsync(key, considerUow: false);
cacheValue.ShouldBeNull();
using (var uow = GetRequiredService<IUnitOfWorkManager>().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<IUnitOfWorkManager>().Begin())
{
var personCache = GetRequiredService<IDistributedCache<PersonCacheItem>>();
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<IDistributedCache<PersonCacheItem>>();
var cacheValue = await personCache.GetAsync(key, considerUow: false);
cacheValue.ShouldBeNull();
using (var uow = GetRequiredService<IUnitOfWorkManager>().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<IUnitOfWorkManager>().Begin())
{
var personCache = GetRequiredService<IDistributedCache<PersonCacheItem>>();
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<IDistributedCache<PersonCacheItem>>();
var cacheValue = await personCache.GetAsync(key, considerUow: false);
cacheValue.ShouldBeNull();
using (var uow = GetRequiredService<IUnitOfWorkManager>().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<IUnitOfWorkManager>().Begin())
{
var personCache = GetRequiredService<IDistributedCache<PersonCacheItem>>();
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<IDistributedCache<PersonCacheItem>>();
var cacheValue = await personCache.GetAsync(key, considerUow: false);
cacheValue.ShouldBeNull();
using (var uow = GetRequiredService<IUnitOfWorkManager>().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();
}
}
}
}

@ -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<string> 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<FeatureValueCacheItem> 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<KeyValuePair<string, FeatureValueCacheItem>>();
var cacheItems = new List<KeyValuePair<string, FeatureValueCacheItem>>();
foreach (var featureDefinition in featureDefinitions)
{
var featureValue = featuresDictionary.GetOrDefault(featureDefinition.Name);
cacheItems.Add(
new KeyValuePair<string, FeatureValueCacheItem>(
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)

@ -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<IFeatureManagementStore>();
FeatureValueRepository = GetRequiredService<IFeatureValueRepository>();
UnitOfWorkManager = GetRequiredService<IUnitOfWorkManager>();
}
[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();
}
}
}
}

@ -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<SettingCacheItem> cache,
ISettingDefinitionManager settingDefinitionManager)
{
@ -27,11 +28,13 @@ namespace Volo.Abp.SettingManagement
SettingDefinitionManager = settingDefinitionManager;
}
[UnitOfWork]
public virtual async Task<string> 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<List<SettingValue>> 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<SettingCacheItem> 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<KeyValuePair<string, SettingCacheItem>>();
var cacheItems = new List<KeyValuePair<string, SettingCacheItem>>();
foreach (var settingDefinition in settingDefinitions)
{
var settingValue = settingsDictionary.GetOrDefault(settingDefinition.Name);
cacheItems.Add(
new KeyValuePair<string, SettingCacheItem>(
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)

@ -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<ISettingManagementStore>();
_settingRepository = GetRequiredService<ISettingRepository>();
_testData = GetRequiredService<SettingTestData>();
_unitOfWorkManager= GetRequiredService<IUnitOfWorkManager>();
}
[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();
}
}
}
}

Loading…
Cancel
Save