diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs index 654e60f323..be8fde19f2 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Distributed; @@ -15,10 +16,36 @@ namespace Volo.Abp.Caching /// Represents a distributed cache of type. /// /// The type of cache item being cached. - public class DistributedCache : IDistributedCache + public class DistributedCache : DistributedCache, IDistributedCache where TCacheItem : class { - public ILogger> Logger { get; set; } + public DistributedCache( + IOptions cacheOption, + IOptions distributedCacheOption, + IDistributedCache cache, + ICancellationTokenProvider cancellationTokenProvider, + IDistributedCacheSerializer serializer, + ICurrentTenant currentTenant) : base( + cacheOption: cacheOption, + distributedCacheOption: distributedCacheOption, + cache: cache, + cancellationTokenProvider: cancellationTokenProvider, + serializer: serializer, + currentTenant: currentTenant) + { + } + + } + /// + /// Represents a distributed cache of type. + /// Uses a generic cache key type of type. + /// + /// The type of cache item being cached. + /// The type of cache key being used. + public class DistributedCache : IDistributedCache + where TCacheItem : class + { + public ILogger> Logger { get; set; } protected string CacheName { get; set; } @@ -52,7 +79,7 @@ namespace Volo.Abp.Caching _cacheOption = cacheOption.Value; Cache = cache; CancellationTokenProvider = cancellationTokenProvider; - Logger = NullLogger>.Instance; + Logger = NullLogger>.Instance; Serializer = serializer; CurrentTenant = currentTenant; @@ -60,7 +87,60 @@ namespace Volo.Abp.Caching SetDefaultOptions(); } + protected virtual string NormalizeKey(TCacheKey key) + { + Type type = key.GetType(); + string keyValue = key.ToString(); + + // If complex type of object, override the key value with property concatenation + if (!typeof(IComparable).IsAssignableFrom(type) || type.IsPrimitive || type.IsValueType) + { + var sb = new System.Text.StringBuilder(); + var properties = type.GetProperties().Where(prop => prop.CanRead && prop.CanWrite); + + foreach (var prop in properties) + { + var value = prop.GetValue(key, null); + if (value != null) + { + sb.Append(value.ToString()); + } + } + keyValue = sb.ToString(); + } + var normalizedKey = "c:" + CacheName + ",k:" + _cacheOption.KeyPrefix + keyValue; + + if (!IgnoreMultiTenancy && CurrentTenant.Id.HasValue) + { + normalizedKey = "t:" + CurrentTenant.Id.Value + "," + normalizedKey; + } + + return normalizedKey; + } + + protected virtual DistributedCacheEntryOptions GetDefaultCacheEntryOptions() + { + foreach (var configure in _cacheOption.CacheConfigurators) + { + var options = configure.Invoke(CacheName); + if (options != null) + { + return options; + } + } + return _cacheOption.GlobalCacheEntryOptions; + } + + protected virtual void SetDefaultOptions() + { + CacheName = CacheNameAttribute.GetCacheName(typeof(TCacheItem)); + + //IgnoreMultiTenancy + IgnoreMultiTenancy = typeof(TCacheItem).IsDefined(typeof(IgnoreMultiTenancyAttribute), true); + //Configure default cache entry options + DefaultCacheOptions = GetDefaultCacheEntryOptions(); + } /// /// Gets a cache item with the given key. If no cache item is found for the given key then returns null. /// @@ -68,7 +148,7 @@ namespace Volo.Abp.Caching /// Indicates to throw or hide the exceptions for the distributed cache. /// The cache item, or null. public virtual TCacheItem Get( - string key, + TCacheKey key, bool? hideErrors = null) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -106,7 +186,7 @@ namespace Volo.Abp.Caching /// The for the task. /// The cache item, or null. public virtual async Task GetAsync( - string key, + TCacheKey key, bool? hideErrors = null, CancellationToken token = default) { @@ -131,7 +211,7 @@ namespace Volo.Abp.Caching throw; } - + if (cachedBytes == null) { return null; @@ -139,7 +219,6 @@ namespace Volo.Abp.Caching return Serializer.Deserialize(cachedBytes); } - /// /// Gets or Adds a cache item with the given key. If no cache item is found for the given key then adds a cache item /// provided by delegate and returns the provided cache item. @@ -149,10 +228,10 @@ namespace Volo.Abp.Caching /// The cache options for the factory delegate. /// Indicates to throw or hide the exceptions for the distributed cache. /// The cache item. - public TCacheItem GetOrAdd( - string key, - Func factory, - Func optionsFactory = null, + public virtual TCacheItem GetOrAdd( + TCacheKey key, + Func factory, + Func optionsFactory = null, bool? hideErrors = null) { var value = Get(key, hideErrors); @@ -175,7 +254,6 @@ namespace Volo.Abp.Caching return value; } - /// /// Gets or Adds a cache item with the given key. If no cache item is found for the given key then adds a cache item /// provided by delegate and returns the provided cache item. @@ -186,11 +264,11 @@ namespace Volo.Abp.Caching /// Indicates to throw or hide the exceptions for the distributed cache. /// The for the task. /// The cache item. - public async Task GetOrAddAsync( - string key, - Func> factory, - Func optionsFactory = null, - bool? hideErrors = null, + public virtual async Task GetOrAddAsync( + TCacheKey key, + Func> factory, + Func optionsFactory = null, + bool? hideErrors = null, CancellationToken token = default) { token = CancellationTokenProvider.FallbackToProvider(token); @@ -214,7 +292,6 @@ namespace Volo.Abp.Caching return value; } - /// /// Sets the cache item value for the provided key. /// @@ -223,7 +300,7 @@ namespace Volo.Abp.Caching /// The cache options for the value. /// Indicates to throw or hide the exceptions for the distributed cache. public virtual void Set( - string key, + TCacheKey key, TCacheItem value, DistributedCacheEntryOptions options = null, bool? hideErrors = null) @@ -249,7 +326,6 @@ namespace Volo.Abp.Caching throw; } } - /// /// Sets the cache item value for the provided key. /// @@ -260,7 +336,7 @@ namespace Volo.Abp.Caching /// The for the task. /// The indicating that the operation is asynchronous. public virtual async Task SetAsync( - string key, + TCacheKey key, TCacheItem value, DistributedCacheEntryOptions options = null, bool? hideErrors = null, @@ -288,15 +364,14 @@ namespace Volo.Abp.Caching throw; } } - /// /// Refreshes the cache value of the given key, and resets its sliding expiration timeout. /// /// The key of cached item to be retrieved from the cache. /// Indicates to throw or hide the exceptions for the distributed cache. public virtual void Refresh( - string key, - bool? hideErrors = null) + TCacheKey key, bool? + hideErrors = null) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -315,7 +390,6 @@ namespace Volo.Abp.Caching throw; } } - /// /// Refreshes the cache value of the given key, and resets its sliding expiration timeout. /// @@ -324,7 +398,7 @@ namespace Volo.Abp.Caching /// The for the task. /// The indicating that the operation is asynchronous. public virtual async Task RefreshAsync( - string key, + TCacheKey key, bool? hideErrors = null, CancellationToken token = default) { @@ -345,15 +419,13 @@ namespace Volo.Abp.Caching throw; } } - - /// /// Removes the cache item for given key from cache. /// /// The key of cached item to be retrieved from the cache. /// Indicates to throw or hide the exceptions for the distributed cache. public virtual void Remove( - string key, + TCacheKey key, bool? hideErrors = null) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -372,7 +444,6 @@ namespace Volo.Abp.Caching throw; } } - /// /// Removes the cache item for given key from cache. /// @@ -381,7 +452,7 @@ namespace Volo.Abp.Caching /// The for the task. /// The indicating that the operation is asynchronous. public virtual async Task RemoveAsync( - string key, + TCacheKey key, bool? hideErrors = null, CancellationToken token = default) { @@ -402,364 +473,7 @@ namespace Volo.Abp.Caching throw; } } - - protected virtual string NormalizeKey(string key) - { - var normalizedKey = "c:" + CacheName + ",k:" + _cacheOption.KeyPrefix + key; - - if (!IgnoreMultiTenancy && CurrentTenant.Id.HasValue) - { - normalizedKey = "t:" + CurrentTenant.Id.Value + "," + normalizedKey; - } - - return normalizedKey; - } - - protected virtual DistributedCacheEntryOptions GetDefaultCacheEntryOptions() - { - foreach (var configure in _cacheOption.CacheConfigurators) - { - var options = configure.Invoke(CacheName); - if (options != null) - { - return options; - } - } - return _cacheOption.GlobalCacheEntryOptions; - } - - protected virtual void SetDefaultOptions() - { - CacheName = CacheNameAttribute.GetCacheName(typeof(TCacheItem)); - - //IgnoreMultiTenancy - IgnoreMultiTenancy = typeof(TCacheItem).IsDefined(typeof(IgnoreMultiTenancyAttribute), true); - - //Configure default cache entry options - DefaultCacheOptions = GetDefaultCacheEntryOptions(); - } - } - /// - /// Represents a distributed cache of type. - /// Uses a generic cache key type of type. - /// - /// The type of cache item being cached. - /// The type of cache key being used. - public class DistributedCache : IDistributedCache - where TCacheItem : class - { - public ILogger> Logger { get; set; } - - protected string CacheName { get; set; } - - protected bool IgnoreMultiTenancy { get; set; } - - protected IDistributedCache Cache { get; } - - protected ICancellationTokenProvider CancellationTokenProvider { get; } - - protected IDistributedCacheSerializer Serializer { get; } - - protected ICurrentTenant CurrentTenant { get; } - - protected SemaphoreSlim SyncSemaphore { get; } - - protected DistributedCacheEntryOptions DefaultCacheOptions; - - private readonly CacheOptions _cacheOption; - - private readonly DistributedCacheOptions _distributedCacheOption; - - public DistributedCache( - IOptions cacheOption, - IOptions distributedCacheOption, - IDistributedCache cache, - ICancellationTokenProvider cancellationTokenProvider, - IDistributedCacheSerializer serializer, - ICurrentTenant currentTenant) - { - _distributedCacheOption = distributedCacheOption.Value; - _cacheOption = cacheOption.Value; - Cache = cache; - CancellationTokenProvider = cancellationTokenProvider; - Logger = NullLogger>.Instance; - Serializer = serializer; - CurrentTenant = currentTenant; - - SyncSemaphore = new SemaphoreSlim(1, 1); - - SetDefaultOptions(); - } - protected virtual string NormalizeKey(string key) - { - var normalizedKey = "c:" + CacheName + ",k:" + _cacheOption.KeyPrefix + key; - - if (!IgnoreMultiTenancy && CurrentTenant.Id.HasValue) - { - normalizedKey = "t:" + CurrentTenant.Id.Value + "," + normalizedKey; - } - - return normalizedKey; - } - - protected virtual DistributedCacheEntryOptions GetDefaultCacheEntryOptions() - { - foreach (var configure in _cacheOption.CacheConfigurators) - { - var options = configure.Invoke(CacheName); - if (options != null) - { - return options; - } - } - return _cacheOption.GlobalCacheEntryOptions; - } - - protected virtual void SetDefaultOptions() - { - CacheName = CacheNameAttribute.GetCacheName(typeof(TCacheItem)); - - //IgnoreMultiTenancy - IgnoreMultiTenancy = typeof(TCacheItem).IsDefined(typeof(IgnoreMultiTenancyAttribute), true); - - //Configure default cache entry options - DefaultCacheOptions = GetDefaultCacheEntryOptions(); - } - public virtual TCacheItem Get(TCacheKey key, bool? hideErrors = null) - { - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - - byte[] cachedBytes; - - try - { - cachedBytes = Cache.Get(NormalizeKey(key?.ToString())); - } - catch (Exception ex) - { - if (hideErrors == true) - { - Logger.LogException(ex, LogLevel.Warning); - return null; - } - - throw; - } - - if (cachedBytes == null) - { - return null; - } - - return Serializer.Deserialize(cachedBytes); - } - - public virtual async Task GetAsync(TCacheKey key, bool? hideErrors = null, CancellationToken token = default) - { - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - - byte[] cachedBytes; - - try - { - cachedBytes = await Cache.GetAsync( - NormalizeKey(key.ToString()), - CancellationTokenProvider.FallbackToProvider(token) - ); - } - catch (Exception ex) - { - if (hideErrors == true) - { - Logger.LogException(ex, LogLevel.Warning); - return null; - } - - throw; - } - - if (cachedBytes == null) - { - return null; - } - - return Serializer.Deserialize(cachedBytes); - } - - public virtual TCacheItem GetOrAdd(TCacheKey key, Func factory, Func optionsFactory = null, bool? hideErrors = null) - { - var value = Get(key, hideErrors); - if (value != null) - { - return value; - } - - using (SyncSemaphore.Lock()) - { - value = Get(key, hideErrors); - if (value != null) - { - return value; - } - - value = factory(); - Set(key, value, optionsFactory?.Invoke(), hideErrors); - } - - return value; - } - - public virtual async Task GetOrAddAsync(TCacheKey key, Func> factory, Func optionsFactory = null, bool? hideErrors = null, CancellationToken token = default) - { - token = CancellationTokenProvider.FallbackToProvider(token); - var value = await GetAsync(key, hideErrors, token); - if (value != null) - { - return value; - } - - using (await SyncSemaphore.LockAsync(token)) - { - value = await GetAsync(key, hideErrors, token); - if (value != null) - { - return value; - } - - value = await factory(); - await SetAsync(key, value, optionsFactory?.Invoke(), hideErrors, token); - } - - return value; - } - - public virtual void Set(TCacheKey key, TCacheItem value, DistributedCacheEntryOptions options = null, bool? hideErrors = null) - { - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - - try - { - Cache.Set( - NormalizeKey(key.ToString()), - Serializer.Serialize(value), - options ?? DefaultCacheOptions - ); - } - catch (Exception ex) - { - if (hideErrors == true) - { - Logger.LogException(ex, LogLevel.Warning); - return; - } - - throw; - } - } - - public virtual async Task SetAsync(TCacheKey key, TCacheItem value, DistributedCacheEntryOptions options = null, bool? hideErrors = null, CancellationToken token = default) - { - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - - try - { - await Cache.SetAsync( - NormalizeKey(key.ToString()), - Serializer.Serialize(value), - options ?? DefaultCacheOptions, - CancellationTokenProvider.FallbackToProvider(token) - ); - } - catch (Exception ex) - { - if (hideErrors == true) - { - Logger.LogException(ex, LogLevel.Warning); - return; - } - - throw; - } - } - - public virtual void Refresh(TCacheKey key, bool? hideErrors = null) - { - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - - try - { - Cache.Refresh(NormalizeKey(key.ToString())); - } - catch (Exception ex) - { - if (hideErrors == true) - { - Logger.LogException(ex, LogLevel.Warning); - return; - } - - throw; - } - } - - public virtual async Task RefreshAsync(TCacheKey key, bool? hideErrors = null, CancellationToken token = default) - { - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - - try - { - await Cache.RefreshAsync(NormalizeKey(key.ToString()), CancellationTokenProvider.FallbackToProvider(token)); - } - catch (Exception ex) - { - if (hideErrors == true) - { - Logger.LogException(ex, LogLevel.Warning); - return; - } - - throw; - } - } - - public virtual void Remove(TCacheKey key, bool? hideErrors = null) - { - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - - try - { - Cache.Remove(NormalizeKey(key.ToString())); - } - catch (Exception ex) - { - if (hideErrors == true) - { - Logger.LogException(ex, LogLevel.Warning); - } - - throw; - } - } - - public virtual async Task RemoveAsync(TCacheKey key, bool? hideErrors = null, CancellationToken token = default) - { - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - - try - { - await Cache.RemoveAsync(NormalizeKey(key.ToString()), CancellationTokenProvider.FallbackToProvider(token)); - } - catch (Exception ex) - { - if (hideErrors == true) - { - Logger.LogException(ex, LogLevel.Warning); - return; - } - - throw; - } - } - + } + } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs index 82adeda726..163da05632 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs @@ -10,143 +10,10 @@ namespace Volo.Abp.Caching /// Represents a distributed cache of type. /// /// The type of cache item being cached. - public interface IDistributedCache + public interface IDistributedCache : IDistributedCache where TCacheItem : class { - /// - /// Gets a cache item with the given key. If no cache item is found for the given key then returns null. - /// - /// The key of cached item to be retrieved from the cache. - /// Indicates to throw or hide the exceptions for the distributed cache. - /// The cache item, or null. - TCacheItem Get( - string key, - bool? hideErrors = null - ); - - /// - /// Gets a cache item with the given key. If no cache item is found for the given key then returns null. - /// - /// The key of cached item to be retrieved from the cache. - /// Indicates to throw or hide the exceptions for the distributed cache. - /// The for the task. - /// The cache item, or null. - Task GetAsync( - [NotNull] string key, - bool? hideErrors = null, - CancellationToken token = default - ); - - /// - /// Gets or Adds a cache item with the given key. If no cache item is found for the given key then adds a cache item - /// provided by delegate and returns the provided cache item. - /// - /// The key of cached item to be retrieved from the cache. - /// The factory delegate is used to provide the cache item when no cache item is found for the given . - /// The cache options for the factory delegate. - /// Indicates to throw or hide the exceptions for the distributed cache. - /// The cache item. - TCacheItem GetOrAdd( - string key, - Func factory, - Func optionsFactory = null, - bool? hideErrors = null - ); - /// - /// Gets or Adds a cache item with the given key. If no cache item is found for the given key then adds a cache item - /// provided by delegate and returns the provided cache item. - /// - /// The key of cached item to be retrieved from the cache. - /// The factory delegate is used to provide the cache item when no cache item is found for the given . - /// The cache options for the factory delegate. - /// Indicates to throw or hide the exceptions for the distributed cache. - /// The for the task. - /// The cache item. - Task GetOrAddAsync( - [NotNull] string key, - Func> factory, - Func optionsFactory = null, - bool? hideErrors = null, - CancellationToken token = default - ); - - /// - /// Sets the cache item value for the provided key. - /// - /// The key of cached item to be retrieved from the cache. - /// The cache item value to set in the cache. - /// The cache options for the value. - /// Indicates to throw or hide the exceptions for the distributed cache. - void Set( - string key, - TCacheItem value, - DistributedCacheEntryOptions options = null, - bool? hideErrors = null - ); - - /// - /// Sets the cache item value for the provided key. - /// - /// The key of cached item to be retrieved from the cache. - /// The cache item value to set in the cache. - /// The cache options for the value. - /// Indicates to throw or hide the exceptions for the distributed cache. - /// The for the task. - /// The indicating that the operation is asynchronous. - Task SetAsync( - [NotNull] string key, - [NotNull] TCacheItem value, - [CanBeNull] DistributedCacheEntryOptions options = null, - bool? hideErrors = null, - CancellationToken token = default - ); - - /// - /// Refreshes the cache value of the given key, and resets its sliding expiration timeout. - /// - /// The key of cached item to be retrieved from the cache. - /// Indicates to throw or hide the exceptions for the distributed cache. - void Refresh( - string key, - bool? hideErrors = null - ); - - /// - /// Refreshes the cache value of the given key, and resets its sliding expiration timeout. - /// - /// The key of cached item to be retrieved from the cache. - /// Indicates to throw or hide the exceptions for the distributed cache. - /// The for the task. - /// The indicating that the operation is asynchronous. - Task RefreshAsync( - string key, - bool? hideErrors = null, - CancellationToken token = default - ); - - /// - /// Removes the cache item for given key from cache. - /// - /// The key of cached item to be retrieved from the cache. - /// Indicates to throw or hide the exceptions for the distributed cache. - void Remove( - string key, - bool? hideErrors = null - ); - - /// - /// Removes the cache item for given key from cache. - /// - /// The key of cached item to be retrieved from the cache. - /// Indicates to throw or hide the exceptions for the distributed cache. - /// The for the task. - /// The indicating that the operation is asynchronous. - Task RemoveAsync( - string key, - bool? hideErrors = null, - CancellationToken token = default - ); } /// /// Represents a distributed cache of type. diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs index b802b3e297..93788e0995 100644 --- a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs +++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs @@ -267,5 +267,47 @@ namespace Volo.Abp.Caching cacheItem = await personCache.GetAsync(cacheKey); cacheItem.ShouldBeNull(); } + + [Fact] + public async Task Should_Set_Get_And_Remove_Cache_Items_For_Same_Object_Type_With_Different_CacheKeys() + { + var personCache = GetRequiredService>(); + + var cache1Key = new DummyObjectAsCacheKey { DummyData = "DummyData", DummyInt = 42 }; + var cache2Key = new DummyObjectAsCacheKey { DummyData = "DummyData2", DummyInt = 24 }; + const string personName = "john nash"; + + //Get (not exists yet) + var cacheItem1 = await personCache.GetAsync(cache1Key); + var cacheItem2 = await personCache.GetAsync(cache2Key); + cacheItem1.ShouldBeNull(); + cacheItem2.ShouldBeNull(); + + //Set + cacheItem1 = new PersonCacheItem(personName); + cacheItem2 = new PersonCacheItem(personName); + await personCache.SetAsync(cache1Key, cacheItem1); + await personCache.SetAsync(cache2Key, cacheItem2); + + //Get (it should be available now + cacheItem1 = await personCache.GetAsync(cache1Key); + cacheItem1.ShouldNotBeNull(); + cacheItem1.Name.ShouldBe(personName); + + cacheItem2 = await personCache.GetAsync(cache2Key); + cacheItem2.ShouldNotBeNull(); + cacheItem2.Name.ShouldBe(personName); + + //Remove + await personCache.RemoveAsync(cache1Key); + await personCache.RemoveAsync(cache2Key); + + //Get (not exists since removed) + cacheItem1 = await personCache.GetAsync(cache1Key); + cacheItem1.ShouldBeNull(); + cacheItem2 = await personCache.GetAsync(cache2Key); + cacheItem2.ShouldBeNull(); + } + } } \ No newline at end of file