diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs index 2a44b1a91e..ba59bf039e 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs @@ -21,6 +21,7 @@ namespace Volo.Abp.Caching context.Services.AddDistributedMemoryCache(); context.Services.AddSingleton(typeof(IDistributedCache<>), typeof(DistributedCache<>)); + context.Services.AddSingleton(typeof(IDistributedCache<,>), typeof(DistributedCache<,>)); context.Services.Configure(cacheOptions => { 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 1e24a08196..2a1f109f5c 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs @@ -15,10 +15,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 +78,7 @@ namespace Volo.Abp.Caching _cacheOption = cacheOption.Value; Cache = cache; CancellationTokenProvider = cancellationTokenProvider; - Logger = NullLogger>.Instance; + Logger = NullLogger>.Instance; Serializer = serializer; CurrentTenant = currentTenant; @@ -60,7 +86,41 @@ namespace Volo.Abp.Caching SetDefaultOptions(); } + protected virtual string NormalizeKey(TCacheKey key) + { + var normalizedKey = "c:" + CacheName + ",k:" + _cacheOption.KeyPrefix + key.ToString(); + + 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 +128,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,8 +166,8 @@ namespace Volo.Abp.Caching /// The for the task. /// The cache item, or null. public virtual async Task GetAsync( - string key, - bool? hideErrors = null, + TCacheKey key, + bool? hideErrors = null, CancellationToken token = default) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -131,7 +191,7 @@ namespace Volo.Abp.Caching throw; } - + if (cachedBytes == null) { return null; @@ -139,7 +199,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 +208,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 +234,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 +244,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 +272,6 @@ namespace Volo.Abp.Caching return value; } - /// /// Sets the cache item value for the provided key. /// @@ -223,9 +280,9 @@ 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, - TCacheItem value, - DistributedCacheEntryOptions options = null, + TCacheKey key, + TCacheItem value, + DistributedCacheEntryOptions options = null, bool? hideErrors = null) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -249,7 +306,6 @@ namespace Volo.Abp.Caching throw; } } - /// /// Sets the cache item value for the provided key. /// @@ -260,10 +316,10 @@ namespace Volo.Abp.Caching /// The for the task. /// The indicating that the operation is asynchronous. public virtual async Task SetAsync( - string key, - TCacheItem value, - DistributedCacheEntryOptions options = null, - bool? hideErrors = null, + TCacheKey key, + TCacheItem value, + DistributedCacheEntryOptions options = null, + bool? hideErrors = null, CancellationToken token = default) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -288,15 +344,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 +370,6 @@ namespace Volo.Abp.Caching throw; } } - /// /// Refreshes the cache value of the given key, and resets its sliding expiration timeout. /// @@ -324,8 +378,8 @@ namespace Volo.Abp.Caching /// The for the task. /// The indicating that the operation is asynchronous. public virtual async Task RefreshAsync( - string key, - bool? hideErrors = null, + TCacheKey key, + bool? hideErrors = null, CancellationToken token = default) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -345,15 +399,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 +424,6 @@ namespace Volo.Abp.Caching throw; } } - /// /// Removes the cache item for given key from cache. /// @@ -381,8 +432,8 @@ namespace Volo.Abp.Caching /// The for the task. /// The indicating that the operation is asynchronous. public virtual async Task RemoveAsync( - string key, - bool? hideErrors = null, + TCacheKey key, + bool? hideErrors = null, CancellationToken token = default) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -401,42 +452,8 @@ 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(); - } - } + } + + } + } \ 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 23bd6f4cf2..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,7 +10,18 @@ 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 + { + + } + /// + /// 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 interface IDistributedCache where TCacheItem : class { /// @@ -20,7 +31,7 @@ namespace Volo.Abp.Caching /// Indicates to throw or hide the exceptions for the distributed cache. /// The cache item, or null. TCacheItem Get( - string key, + TCacheKey key, bool? hideErrors = null ); @@ -32,7 +43,7 @@ namespace Volo.Abp.Caching /// The for the task. /// The cache item, or null. Task GetAsync( - [NotNull] string key, + [NotNull] TCacheKey key, bool? hideErrors = null, CancellationToken token = default ); @@ -47,7 +58,7 @@ namespace Volo.Abp.Caching /// Indicates to throw or hide the exceptions for the distributed cache. /// The cache item. TCacheItem GetOrAdd( - string key, + TCacheKey key, Func factory, Func optionsFactory = null, bool? hideErrors = null @@ -64,7 +75,7 @@ namespace Volo.Abp.Caching /// The for the task. /// The cache item. Task GetOrAddAsync( - [NotNull] string key, + [NotNull] TCacheKey key, Func> factory, Func optionsFactory = null, bool? hideErrors = null, @@ -79,7 +90,7 @@ namespace Volo.Abp.Caching /// The cache options for the value. /// Indicates to throw or hide the exceptions for the distributed cache. void Set( - string key, + TCacheKey key, TCacheItem value, DistributedCacheEntryOptions options = null, bool? hideErrors = null @@ -95,7 +106,7 @@ namespace Volo.Abp.Caching /// The for the task. /// The indicating that the operation is asynchronous. Task SetAsync( - [NotNull] string key, + [NotNull] TCacheKey key, [NotNull] TCacheItem value, [CanBeNull] DistributedCacheEntryOptions options = null, bool? hideErrors = null, @@ -108,7 +119,7 @@ namespace Volo.Abp.Caching /// The key of cached item to be retrieved from the cache. /// Indicates to throw or hide the exceptions for the distributed cache. void Refresh( - string key, + TCacheKey key, bool? hideErrors = null ); @@ -120,7 +131,7 @@ namespace Volo.Abp.Caching /// The for the task. /// The indicating that the operation is asynchronous. Task RefreshAsync( - string key, + TCacheKey key, bool? hideErrors = null, CancellationToken token = default ); @@ -131,7 +142,7 @@ namespace Volo.Abp.Caching /// The key of cached item to be retrieved from the cache. /// Indicates to throw or hide the exceptions for the distributed cache. void Remove( - string key, + TCacheKey key, bool? hideErrors = null ); @@ -143,7 +154,7 @@ namespace Volo.Abp.Caching /// The for the task. /// The indicating that the operation is asynchronous. Task RemoveAsync( - string key, + TCacheKey key, bool? hideErrors = null, CancellationToken token = default ); diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs index fc6b443893..927448463a 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 @@ -122,5 +122,192 @@ namespace Volo.Abp.Caching cacheItem1.ShouldNotBeNull(); } + + [Fact] + public async Task Should_Set_Get_And_Remove_Cache_Items_With_Integer_Type_CacheKey() + { + var personCache = GetRequiredService>(); + + var cacheKey = 42; + const string personName = "john nash"; + + //Get (not exists yet) + var cacheItem = await personCache.GetAsync(cacheKey); + cacheItem.ShouldBeNull(); + + //Set + cacheItem = new PersonCacheItem(personName); + await personCache.SetAsync(cacheKey, cacheItem); + + //Get (it should be available now + cacheItem = await personCache.GetAsync(cacheKey); + cacheItem.ShouldNotBeNull(); + cacheItem.Name.ShouldBe(personName); + + //Remove + await personCache.RemoveAsync(cacheKey); + + //Get (not exists since removed) + cacheItem = await personCache.GetAsync(cacheKey); + cacheItem.ShouldBeNull(); + } + + [Fact] + public async Task GetOrAddAsync_With_Integer_Type_CacheKey() + { + var personCache = GetRequiredService>(); + + 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.GetOrAddAsync(cacheKey, + async () => + { + factoryExecuted = true; + return new PersonCacheItem(personName); + }); + + factoryExecuted.ShouldBeTrue(); + cacheItem.Name.ShouldBe(personName); + + //This time, it will not execute the factory + + factoryExecuted = false; + + cacheItem = await personCache.GetOrAddAsync(cacheKey, + async () => + { + factoryExecuted = true; + return new PersonCacheItem(personName); + }); + + factoryExecuted.ShouldBeFalse(); + cacheItem.Name.ShouldBe(personName); + } + + [Fact] + public async Task SameClassName_But_DiffNamespace_Should_Not_Use_Same_Cache_With_Integer_CacheKey() + { + var personCache = GetRequiredService>(); + var otherPersonCache = GetRequiredService>(); + + + var cacheKey = 42; + const string personName = "john nash"; + + //Get (not exists yet) + var cacheItem = await personCache.GetAsync(cacheKey); + cacheItem.ShouldBeNull(); + + var cacheItem1 = await otherPersonCache.GetAsync(cacheKey); + cacheItem1.ShouldBeNull(); + + //Set + cacheItem = new PersonCacheItem(personName); + await personCache.SetAsync(cacheKey, cacheItem); + + //Get (it should be available now, but otherPersonCache not exists now. + cacheItem = await personCache.GetAsync(cacheKey); + cacheItem.ShouldNotBeNull(); + cacheItem.Name.ShouldBe(personName); + + cacheItem1 = await otherPersonCache.GetAsync(cacheKey); + cacheItem1.ShouldBeNull(); + + //set other person cache + cacheItem1 = new Sail.Testing.Caching.PersonCacheItem(personName); + await otherPersonCache.SetAsync(cacheKey, cacheItem1); + + cacheItem1 = await otherPersonCache.GetAsync(cacheKey); + cacheItem1.ShouldNotBeNull(); + cacheItem1.Name.ShouldBe(personName); + + //Remove + await personCache.RemoveAsync(cacheKey); + + + //Get (not exists since removed) + cacheItem = await personCache.GetAsync(cacheKey); + cacheItem.ShouldBeNull(); + + cacheItem1 = await otherPersonCache.GetAsync(cacheKey); + cacheItem1.ShouldNotBeNull(); + + } + + [Fact] + public async Task Should_Set_Get_And_Remove_Cache_Items_With_Object_Type_CacheKey() + { + var personCache = GetRequiredService>(); + + var cacheKey = new ComplexObjectAsCacheKey { Name = "DummyData", Age = 42 }; + const string personName = "john nash"; + + //Get (not exists yet) + var cacheItem = await personCache.GetAsync(cacheKey); + cacheItem.ShouldBeNull(); + + //Set + cacheItem = new PersonCacheItem(personName); + await personCache.SetAsync(cacheKey, cacheItem); + + //Get (it should be available now + cacheItem = await personCache.GetAsync(cacheKey); + cacheItem.ShouldNotBeNull(); + cacheItem.Name.ShouldBe(personName); + + //Remove + await personCache.RemoveAsync(cacheKey); + + //Get (not exists since removed) + 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 ComplexObjectAsCacheKey { Name = "John", Age = 42 }; + var cache2Key = new ComplexObjectAsCacheKey { Name = "Jenny", Age = 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 diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/PersonCacheItem.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/PersonCacheItem.cs index b6c6f1dd0a..7ea37ffd26 100644 --- a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/PersonCacheItem.cs +++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/PersonCacheItem.cs @@ -1,5 +1,6 @@ using System; - +using System.Linq; + namespace Volo.Abp.Caching { [Serializable] @@ -17,7 +18,33 @@ namespace Volo.Abp.Caching Name = name; } } + + public class ComplexObjectAsCacheKey + { + public string Name { get; set; } + public int Age { get; set; } + + public override string ToString() + { + // Return selective fields + //return $"{Name}_{Age}"; + // Return all the fields concatenated + var sb = new System.Text.StringBuilder(); + var properties = this.GetType().GetProperties() + .Where(prop => prop.CanRead && prop.CanWrite); + foreach (var prop in properties) + { + var value = prop.GetValue(this, null); + if (value != null) + { + sb.Append(value.ToString()); + } + } + return sb.ToString(); + } + } } + namespace Sail.Testing.Caching { [Serializable]