mirror of https://github.com/abpframework/abp.git
10 changed files with 1210 additions and 0 deletions
@ -0,0 +1,459 @@ |
|||
using System; |
|||
using System.Buffers; |
|||
using System.Collections.Concurrent; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Caching.Distributed; |
|||
using Microsoft.Extensions.Caching.Hybrid; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.ExceptionHandling; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.Threading; |
|||
using Volo.Abp.Uow; |
|||
|
|||
namespace Volo.Abp.Caching.Hybrid; |
|||
|
|||
/// <summary>
|
|||
/// Represents a hybrid cache of <typeparamref name="TCacheItem"/> items.
|
|||
/// </summary>
|
|||
/// <typeparam name="TCacheItem">The type of the cache item being cached.</typeparam>
|
|||
public class AbpHybridCache<TCacheItem> : IHybridCache<TCacheItem> |
|||
where TCacheItem : class |
|||
{ |
|||
public IHybridCache<TCacheItem, string> InternalCache { get; } |
|||
|
|||
public AbpHybridCache(IHybridCache<TCacheItem, string> internalCache) |
|||
{ |
|||
InternalCache = internalCache; |
|||
} |
|||
|
|||
public virtual async Task<TCacheItem?> GetOrCreateAsync(string key, Func<Task<TCacheItem>> factory, Func<HybridCacheEntryOptions>? optionsFactory = null, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default) |
|||
{ |
|||
return await InternalCache.GetOrCreateAsync(key, factory, optionsFactory, hideErrors, considerUow, token); |
|||
} |
|||
|
|||
public virtual async Task SetAsync(string key, TCacheItem value, HybridCacheEntryOptions? options = null, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default) |
|||
{ |
|||
await InternalCache.SetAsync(key, value, options, hideErrors, considerUow, token); |
|||
} |
|||
|
|||
public virtual async Task RemoveAsync(string key, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default) |
|||
{ |
|||
await InternalCache.RemoveAsync(key, hideErrors, considerUow, token); |
|||
} |
|||
|
|||
public virtual async Task RemoveManyAsync(IEnumerable<string> keys, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default) |
|||
{ |
|||
await InternalCache.RemoveManyAsync(keys, hideErrors, considerUow, token); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Represents a hybrid cache of <typeparamref name="TCacheItem"/> items.
|
|||
/// Uses <typeparamref name="TCacheKey"/> as the key type.
|
|||
/// </summary>
|
|||
/// <typeparam name="TCacheItem">The type of cache item being cached.</typeparam>
|
|||
/// <typeparam name="TCacheKey">The type of cache key being used.</typeparam>
|
|||
public class AbpHybridCache<TCacheItem, TCacheKey> : IHybridCache<TCacheItem, TCacheKey> |
|||
where TCacheItem : class |
|||
where TCacheKey : notnull |
|||
{ |
|||
public const string UowCacheName = "AbpHybridCache"; |
|||
|
|||
public ILogger<AbpHybridCache<TCacheItem, TCacheKey>> Logger { get; set; } |
|||
|
|||
protected string CacheName { get; set; } = default!; |
|||
|
|||
protected bool IgnoreMultiTenancy { get; set; } |
|||
|
|||
protected IServiceProvider ServiceProvider { get; } |
|||
|
|||
protected HybridCache HybridCache { get; } |
|||
|
|||
protected IDistributedCache DistributedCacheCache { get; } |
|||
|
|||
protected ICancellationTokenProvider CancellationTokenProvider { get; } |
|||
|
|||
protected IDistributedCacheKeyNormalizer KeyNormalizer { get; } |
|||
|
|||
protected IServiceScopeFactory ServiceScopeFactory { get; } |
|||
|
|||
protected IUnitOfWorkManager UnitOfWorkManager { get; } |
|||
|
|||
protected SemaphoreSlim SyncSemaphore { get; } |
|||
|
|||
protected HybridCacheEntryOptions DefaultCacheOptions = default!; |
|||
|
|||
protected AbpHybridCacheOptions DistributedCacheOption { get; } |
|||
|
|||
public AbpHybridCache( |
|||
IServiceProvider serviceProvider, |
|||
IOptions<AbpHybridCacheOptions> distributedCacheOption, |
|||
HybridCache hybridCache, |
|||
IDistributedCache distributedCache, |
|||
ICancellationTokenProvider cancellationTokenProvider, |
|||
IDistributedCacheSerializer serializer, |
|||
IDistributedCacheKeyNormalizer keyNormalizer, |
|||
IServiceScopeFactory serviceScopeFactory, |
|||
IUnitOfWorkManager unitOfWorkManager) |
|||
{ |
|||
ServiceProvider = serviceProvider; |
|||
DistributedCacheOption = distributedCacheOption.Value; |
|||
HybridCache = hybridCache; |
|||
DistributedCacheCache = distributedCache; |
|||
CancellationTokenProvider = cancellationTokenProvider; |
|||
Logger = NullLogger<AbpHybridCache<TCacheItem, TCacheKey>>.Instance; |
|||
KeyNormalizer = keyNormalizer; |
|||
ServiceScopeFactory = serviceScopeFactory; |
|||
UnitOfWorkManager = unitOfWorkManager; |
|||
|
|||
SyncSemaphore = new SemaphoreSlim(1, 1); |
|||
|
|||
SetDefaultOptions(); |
|||
} |
|||
|
|||
protected virtual string NormalizeKey(TCacheKey key) |
|||
{ |
|||
return KeyNormalizer.NormalizeKey( |
|||
new DistributedCacheKeyNormalizeArgs( |
|||
key.ToString()!, |
|||
CacheName, |
|||
IgnoreMultiTenancy |
|||
) |
|||
); |
|||
} |
|||
|
|||
protected virtual HybridCacheEntryOptions GetDefaultCacheEntryOptions() |
|||
{ |
|||
foreach (var configure in DistributedCacheOption.CacheConfigurators) |
|||
{ |
|||
var options = configure.Invoke(CacheName); |
|||
if (options != null) |
|||
{ |
|||
return options; |
|||
} |
|||
} |
|||
|
|||
return DistributedCacheOption.GlobalHybridCacheEntryOptions; |
|||
} |
|||
|
|||
protected virtual void SetDefaultOptions() |
|||
{ |
|||
CacheName = CacheNameAttribute.GetCacheName(typeof(TCacheItem)); |
|||
|
|||
//IgnoreMultiTenancy
|
|||
IgnoreMultiTenancy = typeof(TCacheItem).IsDefined(typeof(IgnoreMultiTenancyAttribute), true); |
|||
|
|||
//Configure default cache entry options
|
|||
DefaultCacheOptions = GetDefaultCacheEntryOptions(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or Creates a cache item with the given key. If no cache item is found for the given key then adds a cache item
|
|||
/// provided by <paramref name="factory" /> delegate and returns the provided cache item.
|
|||
/// </summary>
|
|||
/// <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="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?> GetOrCreateAsync( |
|||
TCacheKey key, |
|||
Func<Task<TCacheItem>> factory, |
|||
Func<HybridCacheEntryOptions>? optionsFactory = null, |
|||
bool? hideErrors = null, |
|||
bool considerUow = false, |
|||
CancellationToken token = default) |
|||
{ |
|||
token = CancellationTokenProvider.FallbackToProvider(token); |
|||
hideErrors = hideErrors ?? DistributedCacheOption.HideErrors; |
|||
|
|||
TCacheItem? value = null; |
|||
|
|||
if (!considerUow) |
|||
{ |
|||
try |
|||
{ |
|||
value = await HybridCache.GetOrCreateAsync( |
|||
key: NormalizeKey(key), |
|||
factory: async cancel => await factory(), |
|||
options: optionsFactory?.Invoke(), |
|||
tags: null, |
|||
cancellationToken: token); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
if (hideErrors == true) |
|||
{ |
|||
await HandleExceptionAsync(ex); |
|||
return null; |
|||
} |
|||
|
|||
throw; |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
|
|||
try |
|||
{ |
|||
using (await SyncSemaphore.LockAsync(token)) |
|||
{ |
|||
if (ShouldConsiderUow(considerUow)) |
|||
{ |
|||
value = GetUnitOfWorkCache().GetOrDefault(key)?.GetUnRemovedValueOrNull(); |
|||
if (value != null) |
|||
{ |
|||
return value; |
|||
} |
|||
} |
|||
|
|||
var bytes = await DistributedCacheCache.GetAsync(NormalizeKey(key), token); |
|||
if (bytes != null) |
|||
{ |
|||
return ResolveSerializer().Deserialize(new ReadOnlySequence<byte>(bytes, 0, bytes.Length));; |
|||
} |
|||
|
|||
value = await factory(); |
|||
|
|||
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(), hideErrors, considerUow, token); |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
if (hideErrors == true) |
|||
{ |
|||
await HandleExceptionAsync(ex); |
|||
return null; |
|||
} |
|||
|
|||
throw; |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
|
|||
/// <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="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 <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
|
|||
public virtual async Task SetAsync( |
|||
TCacheKey key, |
|||
TCacheItem value, |
|||
HybridCacheEntryOptions? options = null, |
|||
bool? hideErrors = null, |
|||
bool considerUow = false, |
|||
CancellationToken token = default) |
|||
{ |
|||
async Task SetRealCache() |
|||
{ |
|||
token = CancellationTokenProvider.FallbackToProvider(token); |
|||
hideErrors = hideErrors ?? DistributedCacheOption.HideErrors; |
|||
|
|||
try |
|||
{ |
|||
await HybridCache.SetAsync( |
|||
key: NormalizeKey(key), |
|||
value: value, |
|||
options: options ?? DefaultCacheOptions, |
|||
tags: null, |
|||
cancellationToken: token |
|||
); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
if (hideErrors == true) |
|||
{ |
|||
await HandleExceptionAsync(ex); |
|||
return; |
|||
} |
|||
|
|||
throw; |
|||
} |
|||
} |
|||
|
|||
if (ShouldConsiderUow(considerUow)) |
|||
{ |
|||
var uowCache = GetUnitOfWorkCache(); |
|||
if (uowCache.TryGetValue(key, out _)) |
|||
{ |
|||
uowCache[key].SetValue(value); |
|||
} |
|||
else |
|||
{ |
|||
uowCache.Add(key, new UnitOfWorkCacheItem<TCacheItem>(value)); |
|||
} |
|||
|
|||
UnitOfWorkManager.Current?.OnCompleted(SetRealCache); |
|||
} |
|||
else |
|||
{ |
|||
await SetRealCache(); |
|||
} |
|||
} |
|||
|
|||
/// <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="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 <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
|
|||
public virtual async Task RemoveAsync( |
|||
TCacheKey key, |
|||
bool? hideErrors = null, |
|||
bool considerUow = false, |
|||
CancellationToken token = default) |
|||
{ |
|||
await RemoveManyAsync(new[] { key }, hideErrors, considerUow, token); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes the cache items for given keys from cache.
|
|||
/// </summary>
|
|||
/// <param name="keys">The keys of cached items 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 <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
|
|||
public async Task RemoveManyAsync( |
|||
IEnumerable<TCacheKey> keys, |
|||
bool? hideErrors = null, |
|||
bool considerUow = false, |
|||
CancellationToken token = default) |
|||
{ |
|||
var keyArray = keys.ToArray(); |
|||
|
|||
async Task RemoveRealCache() |
|||
{ |
|||
hideErrors = hideErrors ?? DistributedCacheOption.HideErrors; |
|||
|
|||
try |
|||
{ |
|||
await HybridCache.RemoveAsync( |
|||
keyArray.Select(NormalizeKey), token); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
if (hideErrors == true) |
|||
{ |
|||
await HandleExceptionAsync(ex); |
|||
return; |
|||
} |
|||
|
|||
throw; |
|||
} |
|||
} |
|||
|
|||
if (ShouldConsiderUow(considerUow)) |
|||
{ |
|||
var uowCache = GetUnitOfWorkCache(); |
|||
|
|||
foreach (var key in keyArray) |
|||
{ |
|||
if (uowCache.TryGetValue(key, out _)) |
|||
{ |
|||
uowCache[key].RemoveValue(); |
|||
} |
|||
} |
|||
|
|||
UnitOfWorkManager.Current?.OnCompleted(RemoveRealCache); |
|||
} |
|||
else |
|||
{ |
|||
await RemoveRealCache(); |
|||
} |
|||
} |
|||
|
|||
protected virtual async Task HandleExceptionAsync(Exception ex) |
|||
{ |
|||
Logger.LogException(ex, LogLevel.Warning); |
|||
|
|||
using (var scope = ServiceScopeFactory.CreateScope()) |
|||
{ |
|||
await scope.ServiceProvider |
|||
.GetRequiredService<IExceptionNotifier>() |
|||
.NotifyAsync(new ExceptionNotificationContext(ex, LogLevel.Warning)); |
|||
} |
|||
} |
|||
|
|||
protected virtual bool ShouldConsiderUow(bool considerUow) |
|||
{ |
|||
return considerUow && UnitOfWorkManager.Current != null; |
|||
} |
|||
|
|||
protected virtual string GetUnitOfWorkCacheKey() |
|||
{ |
|||
return UowCacheName + CacheName; |
|||
} |
|||
|
|||
protected virtual Dictionary<TCacheKey, UnitOfWorkCacheItem<TCacheItem>> GetUnitOfWorkCache() |
|||
{ |
|||
if (UnitOfWorkManager.Current == null) |
|||
{ |
|||
throw new AbpException($"There is no active UOW."); |
|||
} |
|||
|
|||
return UnitOfWorkManager.Current.GetOrAddItem(GetUnitOfWorkCacheKey(), |
|||
key => new Dictionary<TCacheKey, UnitOfWorkCacheItem<TCacheItem>>()); |
|||
} |
|||
|
|||
private readonly ConcurrentDictionary<Type, object> _serializersCache = new(); |
|||
|
|||
protected virtual IHybridCacheSerializer<TCacheItem> ResolveSerializer() |
|||
{ |
|||
if (_serializersCache.TryGetValue(typeof(TCacheItem), out var serializer)) |
|||
{ |
|||
return serializer.As<IHybridCacheSerializer<TCacheItem>>(); |
|||
} |
|||
|
|||
serializer = ServiceProvider.GetService<IHybridCacheSerializer<TCacheItem>>(); |
|||
if (serializer is null) |
|||
{ |
|||
var factories = ServiceProvider.GetServices<IHybridCacheSerializerFactory>().ToArray(); |
|||
Array.Reverse(factories); |
|||
foreach (var factory in factories) |
|||
{ |
|||
if (factory.TryCreateSerializer<TCacheItem>(out var current)) |
|||
{ |
|||
serializer = current; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (serializer is null) |
|||
{ |
|||
throw new InvalidOperationException($"No {nameof(IHybridCacheSerializer<TCacheItem>)} configured for type '{typeof(TCacheItem).Name}'"); |
|||
} |
|||
|
|||
return serializer.As<IHybridCacheSerializer<TCacheItem>>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
using System.Buffers; |
|||
using System.Text.Json; |
|||
using Microsoft.Extensions.Caching.Hybrid; |
|||
|
|||
namespace Volo.Abp.Caching.Hybrid; |
|||
|
|||
public class AbpHybridCacheJsonSerializer<T> : IHybridCacheSerializer<T> |
|||
{ |
|||
protected JsonSerializerOptions JsonSerializerOptions { get; } |
|||
|
|||
public AbpHybridCacheJsonSerializer(JsonSerializerOptions jsonSerializerOptions) |
|||
{ |
|||
JsonSerializerOptions = jsonSerializerOptions; |
|||
} |
|||
|
|||
public virtual T Deserialize(ReadOnlySequence<byte> source) |
|||
{ |
|||
var reader = new Utf8JsonReader(source); |
|||
return JsonSerializer.Deserialize<T>(ref reader, JsonSerializerOptions)!; |
|||
} |
|||
|
|||
public virtual void Serialize(T value, IBufferWriter<byte> target) |
|||
{ |
|||
using var writer = new Utf8JsonWriter(target); |
|||
JsonSerializer.Serialize<T>(writer, value, JsonSerializerOptions); |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
using Microsoft.Extensions.Caching.Hybrid; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.Json.SystemTextJson; |
|||
|
|||
namespace Volo.Abp.Caching.Hybrid; |
|||
|
|||
public class AbpHybridCacheJsonSerializerFactory : IHybridCacheSerializerFactory |
|||
{ |
|||
protected IOptions<AbpSystemTextJsonSerializerOptions> Options { get; } |
|||
|
|||
public AbpHybridCacheJsonSerializerFactory(IOptions<AbpSystemTextJsonSerializerOptions> options) |
|||
{ |
|||
Options = options; |
|||
} |
|||
|
|||
public bool TryCreateSerializer<T>(out IHybridCacheSerializer<T>? serializer) |
|||
{ |
|||
if (typeof(T) == typeof(string) || typeof(T) == typeof(byte[])) |
|||
{ |
|||
serializer = null; |
|||
return false; |
|||
} |
|||
|
|||
serializer = new AbpHybridCacheJsonSerializer<T>(Options.Value.JsonSerializerOptions); |
|||
return true; |
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Microsoft.Extensions.Caching.Hybrid; |
|||
|
|||
namespace Volo.Abp.Caching.Hybrid; |
|||
|
|||
public class AbpHybridCacheOptions |
|||
{ |
|||
/// <summary>
|
|||
/// Throw or hide exceptions for the distributed cache.
|
|||
/// </summary>
|
|||
public bool HideErrors { get; set; } = true; |
|||
|
|||
/// <summary>
|
|||
/// Cache key prefix.
|
|||
/// </summary>
|
|||
public string KeyPrefix { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Global Cache entry options.
|
|||
/// </summary>
|
|||
public HybridCacheEntryOptions GlobalHybridCacheEntryOptions { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// List of all cache configurators.
|
|||
/// (func argument:Name of cache)
|
|||
/// </summary>
|
|||
public List<Func<string, HybridCacheEntryOptions?>> CacheConfigurators { get; set; } //TODO: use a configurator interface instead?
|
|||
|
|||
public AbpHybridCacheOptions() |
|||
{ |
|||
CacheConfigurators = new List<Func<string, HybridCacheEntryOptions?>>(); |
|||
GlobalHybridCacheEntryOptions = new HybridCacheEntryOptions(); |
|||
KeyPrefix = ""; |
|||
} |
|||
|
|||
public void ConfigureCache<TCacheItem>(HybridCacheEntryOptions? options) |
|||
{ |
|||
ConfigureCache(typeof(TCacheItem), options); |
|||
} |
|||
|
|||
public void ConfigureCache(Type cacheItemType, HybridCacheEntryOptions? options) |
|||
{ |
|||
ConfigureCache(CacheNameAttribute.GetCacheName(cacheItemType), options); |
|||
} |
|||
|
|||
public void ConfigureCache(string cacheName, HybridCacheEntryOptions? options) |
|||
{ |
|||
CacheConfigurators.Add(name => cacheName != name ? null : options); |
|||
} |
|||
} |
|||
@ -0,0 +1,93 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.Extensions.Caching.Hybrid; |
|||
|
|||
namespace Volo.Abp.Caching.Hybrid; |
|||
|
|||
/// <summary>
|
|||
/// Represents a hybrid cache of <typeparamref name="TCacheItem"/> items.
|
|||
/// </summary>
|
|||
/// <typeparam name="TCacheItem">The type of the cache item being cached.</typeparam>
|
|||
public interface IHybridCache<TCacheItem> : IHybridCache<TCacheItem, string> |
|||
where TCacheItem : class |
|||
{ |
|||
IHybridCache<TCacheItem, string> InternalCache { get; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Represents a hybrid cache of <typeparamref name="TCacheItem"/> items.
|
|||
/// Uses <typeparamref name="TCacheKey"/> as the key type.
|
|||
/// </summary>
|
|||
/// <typeparam name="TCacheItem">The type of cache item being cached.</typeparam>
|
|||
/// <typeparam name="TCacheKey">The type of cache key being used.</typeparam>
|
|||
public interface IHybridCache<TCacheItem, TCacheKey> |
|||
where TCacheItem : class |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or Creates a cache item with the given key. If no cache item is found for the given key then adds a cache item
|
|||
/// provided by <paramref name="factory" /> delegate and returns the provided cache item.
|
|||
/// </summary>
|
|||
/// <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="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>
|
|||
Task<TCacheItem?> GetOrCreateAsync( |
|||
[NotNull]TCacheKey key, |
|||
Func<Task<TCacheItem>> factory, |
|||
Func<HybridCacheEntryOptions>? optionsFactory = null, |
|||
bool? hideErrors = null, |
|||
bool considerUow = false, |
|||
CancellationToken token = default); |
|||
|
|||
/// <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="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 <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
|
|||
Task SetAsync( |
|||
[NotNull]TCacheKey key, |
|||
TCacheItem value, |
|||
HybridCacheEntryOptions? options = null, |
|||
bool? hideErrors = null, |
|||
bool considerUow = false, |
|||
CancellationToken token = default); |
|||
|
|||
/// <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="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 <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
|
|||
Task RemoveAsync( |
|||
[NotNull]TCacheKey key, |
|||
bool? hideErrors = null, |
|||
bool considerUow = false, |
|||
CancellationToken token = default); |
|||
|
|||
/// <summary>
|
|||
/// Removes the cache items for given keys from cache.
|
|||
/// </summary>
|
|||
/// <param name="keys">The keys of cached items 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 <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
|
|||
Task RemoveManyAsync( |
|||
IEnumerable<TCacheKey> keys, |
|||
bool? hideErrors = null, |
|||
bool considerUow = false, |
|||
CancellationToken token = default); |
|||
} |
|||
@ -0,0 +1,519 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Volo.Abp.Caching.Hybrid; |
|||
using Volo.Abp.Testing; |
|||
using Volo.Abp.Uow; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Caching; |
|||
|
|||
public class HybridCache_Tests : AbpIntegratedTest<AbpCachingTestModule> |
|||
{ |
|||
[Fact] |
|||
public async Task Should_GetOrCreate_Set_And_Remove_Cache_Items() |
|||
{ |
|||
var personCache = GetRequiredService<IHybridCache<PersonCacheItem>>(); |
|||
|
|||
var cacheKey = Guid.NewGuid().ToString(); |
|||
|
|||
//GetOrCreateAsync
|
|||
var cacheItem = await personCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new PersonCacheItem("john nash"))); |
|||
cacheItem.ShouldNotBeNull(); |
|||
cacheItem.Name.ShouldBe("john nash"); |
|||
|
|||
//SetAsync
|
|||
await personCache.SetAsync(cacheKey, new PersonCacheItem("baris")); |
|||
|
|||
//GetOrCreateAsync
|
|||
cacheItem = await personCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new PersonCacheItem("john nash"))); |
|||
cacheItem.ShouldNotBeNull(); |
|||
cacheItem.Name.ShouldBe("baris"); |
|||
|
|||
//RemoveAsync
|
|||
await personCache.RemoveAsync(cacheKey); |
|||
|
|||
//GetOrCreateAsync
|
|||
cacheItem = await personCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new PersonCacheItem("lucas"))); |
|||
cacheItem.ShouldNotBeNull(); |
|||
cacheItem.Name.ShouldBe("lucas"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task GetOrCreateAsync() |
|||
{ |
|||
var personCache = GetRequiredService<IHybridCache<PersonCacheItem>>(); |
|||
|
|||
var cacheKey = Guid.NewGuid().ToString(); |
|||
const string personName = "john nash"; |
|||
|
|||
//Will execute the factory method to create the cache item
|
|||
|
|||
bool factoryExecuted = false; |
|||
|
|||
var cacheItem = await personCache.GetOrCreateAsync(cacheKey, |
|||
() => |
|||
{ |
|||
factoryExecuted = true; |
|||
return Task.FromResult(new PersonCacheItem(personName)); |
|||
}); |
|||
|
|||
factoryExecuted.ShouldBeTrue(); |
|||
cacheItem.ShouldNotBeNull(); |
|||
cacheItem.Name.ShouldBe(personName); |
|||
|
|||
//This time, it will not execute the factory
|
|||
|
|||
factoryExecuted = false; |
|||
|
|||
cacheItem = await personCache.GetOrCreateAsync(cacheKey, |
|||
() => |
|||
{ |
|||
factoryExecuted = true; |
|||
return Task.FromResult(new PersonCacheItem(personName)); |
|||
}); |
|||
|
|||
factoryExecuted.ShouldBeFalse(); |
|||
cacheItem.ShouldNotBeNull(); |
|||
cacheItem.Name.ShouldBe(personName); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task SameClassName_But_DiffNamespace_Should_Not_Use_Same_Cache() |
|||
{ |
|||
var personCache = GetRequiredService<IHybridCache<PersonCacheItem>>(); |
|||
var otherPersonCache = GetRequiredService<IHybridCache<Sail.Testing.Caching.PersonCacheItem>>(); |
|||
|
|||
var cacheKey = Guid.NewGuid().ToString(); |
|||
const string personName = "john nash"; |
|||
|
|||
var cacheItem = await personCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new PersonCacheItem(personName))); |
|||
cacheItem.ShouldNotBeNull(); |
|||
cacheItem.Name.ShouldBe(personName); |
|||
var cacheItem1 = await otherPersonCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new Sail.Testing.Caching.PersonCacheItem(personName))); |
|||
cacheItem1.ShouldNotBeNull(); |
|||
cacheItem1.Name.ShouldBe(personName); |
|||
|
|||
await personCache.RemoveAsync(cacheKey); |
|||
|
|||
cacheItem = await personCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new PersonCacheItem(personName + "1"))); |
|||
cacheItem1 = await otherPersonCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new Sail.Testing.Caching.PersonCacheItem(personName + "1"))); |
|||
|
|||
cacheItem.ShouldNotBeNull(); |
|||
cacheItem.Name.ShouldBe(personName + "1"); |
|||
|
|||
cacheItem1.ShouldNotBeNull(); |
|||
cacheItem1.Name.ShouldBe(personName); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Set_Get_And_Remove_Cache_Items_With_Integer_Type_CacheKey() |
|||
{ |
|||
var personCache = GetRequiredService<IHybridCache<PersonCacheItem, int>>(); |
|||
|
|||
var cacheKey = 42; |
|||
const string personName = "john nash"; |
|||
|
|||
//GetOrCreateAsync
|
|||
var cacheItem = await personCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new PersonCacheItem(personName))); |
|||
cacheItem.ShouldNotBeNull(); |
|||
cacheItem.Name.ShouldBe(personName); |
|||
|
|||
//SetAsync
|
|||
await personCache.SetAsync(cacheKey, new PersonCacheItem("baris")); |
|||
|
|||
//GetOrCreateAsync
|
|||
cacheItem = await personCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new PersonCacheItem(personName))); |
|||
cacheItem.ShouldNotBeNull(); |
|||
cacheItem.Name.ShouldBe("baris"); |
|||
|
|||
//RemoveAsync
|
|||
await personCache.RemoveAsync(cacheKey); |
|||
|
|||
//GetOrCreateAsync
|
|||
cacheItem = await personCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new PersonCacheItem("lucas"))); |
|||
cacheItem.ShouldNotBeNull(); |
|||
cacheItem.Name.ShouldBe("lucas"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task GetOrAddAsync_With_Integer_Type_CacheKey() |
|||
{ |
|||
var personCache = GetRequiredService<IHybridCache<PersonCacheItem, int>>(); |
|||
|
|||
var cacheKey = 42; |
|||
const string personName = "john nash"; |
|||
|
|||
//Will execute the factory method to create the cache item
|
|||
|
|||
bool factoryExecuted = false; |
|||
|
|||
var cacheItem = await personCache.GetOrCreateAsync(cacheKey, |
|||
() => |
|||
{ |
|||
factoryExecuted = true; |
|||
return Task.FromResult(new PersonCacheItem(personName)); |
|||
}); |
|||
|
|||
factoryExecuted.ShouldBeTrue(); |
|||
cacheItem.ShouldNotBeNull(); |
|||
cacheItem.Name.ShouldBe(personName); |
|||
|
|||
//This time, it will not execute the factory
|
|||
|
|||
factoryExecuted = false; |
|||
|
|||
cacheItem = await personCache.GetOrCreateAsync(cacheKey, |
|||
() => |
|||
{ |
|||
factoryExecuted = true; |
|||
return Task.FromResult(new PersonCacheItem(personName)); |
|||
}); |
|||
|
|||
factoryExecuted.ShouldBeFalse(); |
|||
cacheItem.ShouldNotBeNull(); |
|||
cacheItem.Name.ShouldBe(personName); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task SameClassName_But_DiffNamespace_Should_Not_Use_Same_Cache_With_Integer_CacheKey() |
|||
{ |
|||
var personCache = GetRequiredService<IHybridCache<PersonCacheItem, int>>(); |
|||
var otherPersonCache = GetRequiredService<IHybridCache<Sail.Testing.Caching.PersonCacheItem, int>>(); |
|||
|
|||
var cacheKey = 42; |
|||
const string personName = "john nash"; |
|||
|
|||
var cacheItem = await personCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new PersonCacheItem(personName))); |
|||
cacheItem.ShouldNotBeNull(); |
|||
cacheItem.Name.ShouldBe(personName); |
|||
var cacheItem1 = await otherPersonCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new Sail.Testing.Caching.PersonCacheItem(personName))); |
|||
cacheItem1.ShouldNotBeNull(); |
|||
cacheItem1.Name.ShouldBe(personName); |
|||
|
|||
await personCache.RemoveAsync(cacheKey); |
|||
|
|||
cacheItem = await personCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new PersonCacheItem(personName + "1"))); |
|||
cacheItem1 = await otherPersonCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new Sail.Testing.Caching.PersonCacheItem(personName + "1"))); |
|||
|
|||
cacheItem.ShouldNotBeNull(); |
|||
cacheItem.Name.ShouldBe(personName + "1"); |
|||
|
|||
cacheItem1.ShouldNotBeNull(); |
|||
cacheItem1.Name.ShouldBe(personName); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Set_Get_And_Remove_Cache_Items_With_Object_Type_CacheKey() |
|||
{ |
|||
var personCache = GetRequiredService<IHybridCache<PersonCacheItem, ComplexObjectAsCacheKey>>(); |
|||
|
|||
var cacheKey = new ComplexObjectAsCacheKey { Name = "DummyData", Age = 42 }; |
|||
const string personName = "john nash"; |
|||
|
|||
//GetOrCreateAsync
|
|||
var cacheItem = await personCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new PersonCacheItem(personName))); |
|||
cacheItem.ShouldNotBeNull(); |
|||
cacheItem.Name.ShouldBe(personName); |
|||
|
|||
//SetAsync
|
|||
await personCache.SetAsync(cacheKey, new PersonCacheItem("baris")); |
|||
|
|||
//GetOrCreateAsync
|
|||
cacheItem = await personCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new PersonCacheItem(personName))); |
|||
cacheItem.ShouldNotBeNull(); |
|||
cacheItem.Name.ShouldBe("baris"); |
|||
|
|||
//RemoveAsync
|
|||
await personCache.RemoveAsync(cacheKey); |
|||
|
|||
//GetOrCreateAsync
|
|||
cacheItem = await personCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new PersonCacheItem("lucas"))); |
|||
cacheItem.ShouldNotBeNull(); |
|||
cacheItem.Name.ShouldBe("lucas"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Set_Get_And_Remove_Cache_Items_For_Same_Object_Type_With_Different_CacheKeys() |
|||
{ |
|||
var personCache = GetRequiredService<IHybridCache<PersonCacheItem, ComplexObjectAsCacheKey>>(); |
|||
|
|||
var cache1Key = new ComplexObjectAsCacheKey { Name = "John", Age = 42 }; |
|||
var cache2Key = new ComplexObjectAsCacheKey { Name = "Jenny", Age = 24 }; |
|||
const string personName = "john nash"; |
|||
|
|||
//GetOrCreateAsync
|
|||
var cacheItem1 = await personCache.GetOrCreateAsync(cache1Key, () => Task.FromResult(new PersonCacheItem(personName))); |
|||
var cacheItem2 = await personCache.GetOrCreateAsync(cache2Key, () => Task.FromResult(new PersonCacheItem(personName))); |
|||
cacheItem1.ShouldNotBeNull(); |
|||
cacheItem1.Name.ShouldBe(personName); |
|||
cacheItem2.ShouldNotBeNull(); |
|||
cacheItem2.Name.ShouldBe(personName); |
|||
|
|||
//SetAsync
|
|||
cacheItem1 = new PersonCacheItem("baris"); |
|||
cacheItem2 = new PersonCacheItem("jack"); |
|||
await personCache.SetAsync(cache1Key, cacheItem1); |
|||
await personCache.SetAsync(cache2Key, cacheItem2); |
|||
|
|||
//GetOrCreateAsync
|
|||
cacheItem1 = await personCache.GetOrCreateAsync(cache1Key, () => Task.FromResult(new PersonCacheItem(personName))); |
|||
cacheItem2 = await personCache.GetOrCreateAsync(cache2Key, () => Task.FromResult(new PersonCacheItem(personName))); |
|||
cacheItem1.ShouldNotBeNull(); |
|||
cacheItem1.Name.ShouldBe("baris"); |
|||
cacheItem2.ShouldNotBeNull(); |
|||
cacheItem2.Name.ShouldBe("jack"); |
|||
|
|||
//Remove
|
|||
await personCache.RemoveAsync(cache1Key); |
|||
await personCache.RemoveAsync(cache2Key); |
|||
|
|||
//Get (not exists since removed)
|
|||
cacheItem1 = await personCache.GetOrCreateAsync(cache1Key, () => Task.FromResult(new PersonCacheItem("lucas"))); |
|||
cacheItem2 = await personCache.GetOrCreateAsync(cache2Key, () => Task.FromResult(new PersonCacheItem("peter"))); |
|||
cacheItem1.ShouldNotBeNull(); |
|||
cacheItem1.Name.ShouldBe("lucas"); |
|||
cacheItem2.ShouldNotBeNull(); |
|||
cacheItem2.Name.ShouldBe("peter"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Cache_Should_Only_Available_In_Uow_For_GetOrCreateAsync() |
|||
{ |
|||
const string key = "testkey"; |
|||
|
|||
using (var uow = GetRequiredService<IUnitOfWorkManager>().Begin()) |
|||
{ |
|||
var personCache = GetRequiredService<IHybridCache<PersonCacheItem>>(); |
|||
|
|||
var cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john")), considerUow: true); |
|||
cacheValue.ShouldNotBeNull(); |
|||
cacheValue.Name.ShouldBe("john"); |
|||
|
|||
await personCache.SetAsync(key, new PersonCacheItem("lucas"), considerUow: true); |
|||
|
|||
cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john2")), considerUow: true); |
|||
cacheValue.ShouldNotBeNull(); |
|||
cacheValue.Name.ShouldBe("lucas"); |
|||
|
|||
cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john3")), considerUow: false); |
|||
cacheValue.ShouldNotBeNull(); |
|||
cacheValue.Name.ShouldBe("john3"); |
|||
|
|||
uow.OnCompleted(async () => |
|||
{ |
|||
cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john4")), considerUow: false); |
|||
cacheValue.ShouldNotBeNull(); |
|||
cacheValue.Name.ShouldBe("lucas"); |
|||
}); |
|||
|
|||
await uow.CompleteAsync(); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Cache_Should_Rollback_With_Uow_For_GetOrCreateAsync() |
|||
{ |
|||
const string key = "testkey"; |
|||
var personCache = GetRequiredService<IHybridCache<PersonCacheItem>>(); |
|||
|
|||
var cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john")), considerUow: false); |
|||
cacheValue.ShouldNotBeNull(); |
|||
cacheValue.Name.ShouldBe("john"); |
|||
|
|||
using (var uow = GetRequiredService<IUnitOfWorkManager>().Begin()) |
|||
{ |
|||
cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john2")), considerUow: true); |
|||
cacheValue.ShouldNotBeNull(); |
|||
cacheValue.Name.ShouldBe("john"); |
|||
|
|||
await personCache.SetAsync(key, new PersonCacheItem("john3"), considerUow: true); |
|||
|
|||
cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john4")), considerUow: true); |
|||
cacheValue.ShouldNotBeNull(); |
|||
cacheValue.Name.ShouldBe("john3"); |
|||
|
|||
cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john5")), considerUow: false); |
|||
cacheValue.ShouldNotBeNull(); |
|||
cacheValue.Name.ShouldBe("john"); |
|||
} |
|||
|
|||
cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john6")), considerUow: false); |
|||
cacheValue.ShouldNotBeNull(); |
|||
cacheValue.Name.ShouldBe("john"); |
|||
} |
|||
|
|||
[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<IHybridCache<PersonCacheItem>>(); |
|||
|
|||
await personCache.SetAsync(key, new PersonCacheItem("john"), considerUow: true); |
|||
|
|||
var cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john2")), considerUow: true); |
|||
cacheValue.ShouldNotBeNull(); |
|||
cacheValue.Name.ShouldBe("john"); |
|||
|
|||
cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john3")), considerUow: false); |
|||
cacheValue.ShouldNotBeNull(); |
|||
cacheValue.Name.ShouldBe("john3"); |
|||
|
|||
uow.OnCompleted(async () => |
|||
{ |
|||
cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john4")), 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<IHybridCache<PersonCacheItem>>(); |
|||
|
|||
var cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john")), considerUow: false); |
|||
cacheValue.ShouldNotBeNull(); |
|||
cacheValue.Name.ShouldBe("john"); |
|||
|
|||
using (var uow = GetRequiredService<IUnitOfWorkManager>().Begin()) |
|||
{ |
|||
await personCache.SetAsync(key, new PersonCacheItem("john2"), considerUow: true); |
|||
|
|||
cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john3")), considerUow: true); |
|||
cacheValue.ShouldNotBeNull(); |
|||
cacheValue.Name.ShouldBe("john2"); |
|||
|
|||
cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john4")), considerUow: false); |
|||
cacheValue.ShouldNotBeNull(); |
|||
cacheValue.Name.ShouldBe("john"); |
|||
} |
|||
|
|||
cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john5")), considerUow: false); |
|||
cacheValue.ShouldNotBeNull(); |
|||
cacheValue.Name.ShouldBe("john"); |
|||
} |
|||
|
|||
[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<IHybridCache<PersonCacheItem>>(); |
|||
|
|||
await personCache.SetAsync(key, new PersonCacheItem("john"), considerUow: true); |
|||
|
|||
var cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john2")), considerUow: true); |
|||
cacheValue.ShouldNotBeNull(); |
|||
cacheValue.Name.ShouldBe("john"); |
|||
|
|||
await personCache.RemoveAsync(key, considerUow: true); |
|||
|
|||
cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john3")), considerUow: true); |
|||
cacheValue.ShouldNotBeNull(); |
|||
cacheValue.Name.ShouldBe("john3"); |
|||
|
|||
cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john4")), considerUow: false); |
|||
cacheValue.ShouldNotBeNull(); |
|||
cacheValue.Name.ShouldBe("john4"); |
|||
|
|||
uow.OnCompleted(async () => |
|||
{ |
|||
cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john5")), considerUow: false); |
|||
cacheValue.ShouldNotBeNull(); |
|||
cacheValue.Name.ShouldBe("john3"); |
|||
}); |
|||
|
|||
await uow.CompleteAsync(); |
|||
} |
|||
} |
|||
|
|||
public async Task Cache_Should_Rollback_With_Uow_For_RemoveAsync() |
|||
{ |
|||
const string key = "testkey"; |
|||
var personCache = GetRequiredService<IHybridCache<PersonCacheItem>>(); |
|||
|
|||
var cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john")), considerUow: false); |
|||
cacheValue.ShouldNotBeNull(); |
|||
cacheValue.Name.ShouldBe("john"); |
|||
|
|||
using (var uow = GetRequiredService<IUnitOfWorkManager>().Begin()) |
|||
{ |
|||
await personCache.SetAsync(key, new PersonCacheItem("john2"), considerUow: true); |
|||
|
|||
cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john")), considerUow: true); |
|||
cacheValue.ShouldNotBeNull(); |
|||
cacheValue.Name.ShouldBe("john2"); |
|||
|
|||
await personCache.RemoveAsync(key, considerUow: true); |
|||
|
|||
cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john3")), considerUow: true); |
|||
cacheValue.ShouldNotBeNull(); |
|||
cacheValue.Name.ShouldBe("john"); |
|||
|
|||
cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john3")), considerUow: false); |
|||
cacheValue.ShouldNotBeNull(); |
|||
cacheValue.Name.ShouldBe("john"); |
|||
} |
|||
|
|||
cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john")), considerUow: false); |
|||
cacheValue.ShouldNotBeNull(); |
|||
cacheValue.Name.ShouldBe("john"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Remove_Multiple_Items_Async() |
|||
{ |
|||
var testkey = "testkey"; |
|||
var testkey2 = "testkey2"; |
|||
var testkey3 = new[] { testkey, testkey2 }; |
|||
|
|||
var personCache = GetRequiredService<IHybridCache<PersonCacheItem>>(); |
|||
|
|||
var cacheValue = await personCache.GetOrCreateAsync(testkey, () => Task.FromResult(new PersonCacheItem("john"))); |
|||
cacheValue.ShouldNotBeNull(); |
|||
cacheValue.Name.ShouldBe("john"); |
|||
|
|||
cacheValue = await personCache.GetOrCreateAsync(testkey2, () => Task.FromResult(new PersonCacheItem("jack"))); |
|||
cacheValue.ShouldNotBeNull(); |
|||
cacheValue.Name.ShouldBe("jack"); |
|||
|
|||
await personCache.RemoveManyAsync(testkey3); |
|||
|
|||
cacheValue = await personCache.GetOrCreateAsync(testkey, () => Task.FromResult(new PersonCacheItem("john2"))); |
|||
cacheValue.ShouldNotBeNull(); |
|||
cacheValue.Name.ShouldBe("john2"); |
|||
|
|||
cacheValue = await personCache.GetOrCreateAsync(testkey2, () => Task.FromResult(new PersonCacheItem("jack2"))); |
|||
cacheValue.ShouldNotBeNull(); |
|||
cacheValue.Name.ShouldBe("jack2"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Get_Same_Cache_Set_When_Resolve_With_Or_Without_Key() |
|||
{ |
|||
var cache1 = GetRequiredService<IHybridCache<PersonCacheItem>>(); |
|||
var cache2 = GetRequiredService<IHybridCache<PersonCacheItem, string>>(); |
|||
|
|||
cache1.InternalCache.ShouldBe(cache2); |
|||
|
|||
await cache1.SetAsync("john", new PersonCacheItem("john")); |
|||
|
|||
var item1 = await cache1.GetOrCreateAsync("john", () => Task.FromResult(new PersonCacheItem("john2"))); |
|||
item1.ShouldNotBeNull(); |
|||
item1.Name.ShouldBe("john"); |
|||
|
|||
var item2 = await cache1.GetOrCreateAsync("john", () => Task.FromResult(new PersonCacheItem("john3"))); |
|||
item2.ShouldNotBeNull(); |
|||
item2.Name.ShouldBe("john"); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue