diff --git a/src/EasyCaching.Core/EasyCachingAbstractProvider.cs b/src/EasyCaching.Core/EasyCachingAbstractProvider.cs index b4985128..920fdc9e 100644 --- a/src/EasyCaching.Core/EasyCachingAbstractProvider.cs +++ b/src/EasyCaching.Core/EasyCachingAbstractProvider.cs @@ -184,6 +184,55 @@ public async Task FlushAsync(CancellationToken cancellationToken = default) } } + public CacheValue Get(string cacheKey, Func dataRetriever, Func expirationRetriever) + { + var operationId = s_diagnosticListener.WriteGetCacheBefore(new BeforeGetRequestEventData(CachingProviderType.ToString(), Name, nameof(Get), new[] { cacheKey }, expiration)); + Exception e = null; + try + { + if (_lockFactory == null) return BaseGet(cacheKey, dataRetriever, expirationRetriever); + + var value = BaseGet(cacheKey); + if (value.HasValue) return value; + + using (var @lock = _lockFactory.CreateLock(Name, $"{cacheKey}_Lock")) + { + if (!@lock.Lock(_options.SleepMs)) throw new TimeoutException(); + + value = BaseGet(cacheKey); + if (value.HasValue) return value; + + var item = dataRetriever(); + if (item != null || _options.CacheNulls) + { + BaseSet(cacheKey, item, expiration); + + return new CacheValue(item, true); + } + else + { + return CacheValue.NoValue; + } + } + } + catch (Exception ex) + { + e = ex; + throw; + } + finally + { + if (e != null) + { + s_diagnosticListener.WriteGetCacheError(operationId, e); + } + else + { + s_diagnosticListener.WriteGetCacheAfter(operationId); + } + } + } + public CacheValue Get(string cacheKey, Func dataRetriever, TimeSpan expiration) { var operationId = s_diagnosticListener.WriteGetCacheBefore(new BeforeGetRequestEventData(CachingProviderType.ToString(), Name, nameof(Get), new[] { cacheKey }, expiration)); @@ -363,6 +412,16 @@ public async Task>> GetAllAsync(IEnumerable } } + public Task> GetAsync(string cacheKey, Func> dataRetriever, Func> expirationRetriever, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task GetAsync(string cacheKey, Type type, Func> dataRetriever, Func> expirationRetriever, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + public async Task> GetAsync(string cacheKey, Func> dataRetriever, TimeSpan expiration, CancellationToken cancellationToken = default) { var operationId = s_diagnosticListener.WriteGetCacheBefore(new BeforeGetRequestEventData(CachingProviderType.ToString(), Name, nameof(GetAsync), new[] { cacheKey }, expiration)); @@ -947,6 +1006,6 @@ protected SearchKeyPattern ProcessSearchKeyPattern(string pattern) protected string HandleSearchKeyPattern(string pattern) { return pattern.Replace("*", string.Empty); - } + } } } diff --git a/src/EasyCaching.Core/IEasyCachingProviderBase.cs b/src/EasyCaching.Core/IEasyCachingProviderBase.cs index 3c431f21..b77b8f28 100644 --- a/src/EasyCaching.Core/IEasyCachingProviderBase.cs +++ b/src/EasyCaching.Core/IEasyCachingProviderBase.cs @@ -142,6 +142,16 @@ public interface IEasyCachingProviderBase /// The 1st type parameter. CacheValue Get(string cacheKey, Func dataRetriever, TimeSpan expiration); + /// + /// Get the specified cacheKey, dataRetriever and expirationRetriever. + /// + /// The get. + /// Cache key. + /// Data retriever. + /// Expiration retriever. + /// The 1st type parameter. + CacheValue Get(string cacheKey, Func dataRetriever, Func expirationRetriever); + /// /// Gets the specified cacheKey, dataRetriever and expiration async. /// @@ -153,6 +163,28 @@ public interface IEasyCachingProviderBase /// The 1st type parameter. Task> GetAsync(string cacheKey, Func> dataRetriever, TimeSpan expiration, CancellationToken cancellationToken = default); + /// + /// Gets the specified cacheKey, dataRetriever and expirationRetriever async. + /// + /// The async. + /// Cache key. + /// Data retriever. + /// Expiration retriever. + /// + /// The 1st type parameter. + Task> GetAsync(string cacheKey, Func> dataRetriever, Func> expirationRetriever, CancellationToken cancellationToken = default); + + /// + /// Gets the specified cacheKey, type, dataRetriever and expirationRetriever async. + /// + /// The async. + /// Cache key. + /// Object Type. + /// Data retriever. + /// Expiration retriever. + /// + Task GetAsync(string cacheKey, Type type, Func> dataRetriever, Func> expirationRetriever, CancellationToken cancellationToken = default); + /// /// Removes cached item by cachekey's prefix. /// diff --git a/src/EasyCaching.HybridCache/HybridCachingProvider.cs b/src/EasyCaching.HybridCache/HybridCachingProvider.cs index b4f46f23..d6d203dd 100644 --- a/src/EasyCaching.HybridCache/HybridCachingProvider.cs +++ b/src/EasyCaching.HybridCache/HybridCachingProvider.cs @@ -261,7 +261,6 @@ public CacheValue Get(string cacheKey) // 但這有 async 問題,async 下,直接傳入 expiration 是 sync,但需要延後取得 expiration 是 async。 // 不過確實可以用 Task.FromResult(expiration); 來強制變成 async 界面 // https://github.com/dotnetcore/EasyCaching/pull/480/files#diff-d8769ba5cc06870491bb7918cb090bcfc19e1f9b56b545e8c023063bbf67c259L166 - TimeSpan ts = GetExpiration(cacheKey); var result = _localCache.Get( cacheKey, () => @@ -278,7 +277,8 @@ public CacheValue Get(string cacheKey) throw; } return value; - }, ts); + }, + () => GetExpiration(cacheKey)); return result; } @@ -292,42 +292,26 @@ public CacheValue Get(string cacheKey) public async Task> GetAsync(string cacheKey, CancellationToken cancellationToken = default) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - - var cacheValue = await _localCache.GetAsync(cacheKey, cancellationToken); - - if (cacheValue.HasValue) - { - return cacheValue; - } - - LogMessage($"local cache can not get the value of {cacheKey}"); - - try - { - cacheValue = await _distributedCache.GetAsync(cacheKey, cancellationToken); - } - catch (Exception ex) - { - LogMessage($"distributed cache get error, [{cacheKey}]", ex); - - if (_options.ThrowIfDistributedCacheError) + var result = await _localCache.GetAsync( + cacheKey, + async () => { - throw; - } - } - - if (cacheValue.HasValue) - { - TimeSpan ts = await GetExpirationAsync(cacheKey, cancellationToken); - - await _localCache.SetAsync(cacheKey, cacheValue.Value, ts, cancellationToken); - - return cacheValue; - } - - LogMessage($"distributed cache can not get the value of {cacheKey}"); - - return CacheValue.NoValue; + var value = default(T); + try + { + value = (await _distributedCache.GetAsync(cacheKey, cancellationToken)).Value; + } + catch (Exception ex) + { + LogMessage($"distributed cache get error, [{cacheKey}]", ex); + if (_options.ThrowIfDistributedCacheError) + throw; + } + return value; + }, + () => GetExpirationAsync(cacheKey, cancellationToken), + cancellationToken); + return result; } /// @@ -687,7 +671,20 @@ public CacheValue Get(string cacheKey, Func dataRetriever, TimeSpan exp { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); - TimeSpan ts = GetExpiration(cacheKey); + return Get(cacheKey, dataRetriever, () => expiration); + } + + /// + /// Get the specified cacheKey, dataRetriever and expirationRetriever. + /// + /// The get. + /// Cache key. + /// Data retriever. + /// Expiration retriever. + /// The 1st type parameter. + public CacheValue Get(string cacheKey, Func dataRetriever, Func expirationRetriever) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); var result = _localCache.Get( cacheKey, () => @@ -695,7 +692,7 @@ public CacheValue Get(string cacheKey, Func dataRetriever, TimeSpan exp var value = default(T); try { - value = _distributedCache.Get(cacheKey, dataRetriever, expiration).Value; + value = _distributedCache.Get(cacheKey, dataRetriever, expirationRetriever).Value; } catch (Exception ex) { @@ -704,7 +701,8 @@ public CacheValue Get(string cacheKey, Func dataRetriever, TimeSpan exp throw; } return value; - }, ts); + }, + () => GetExpiration(cacheKey)); return result; } @@ -721,38 +719,76 @@ public async Task> GetAsync(string cacheKey, Func> data { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); + return await GetAsync(cacheKey, dataRetriever, () => Task.FromResult(expiration), cancellationToken); + } - var result = await _localCache.GetAsync(cacheKey, cancellationToken); - - if (result.HasValue) - { - return result; - } - - try - { - result = await _distributedCache.GetAsync(cacheKey, dataRetriever, expiration, cancellationToken); - } - catch (Exception ex) - { - LogMessage($"get async with data retriever from distributed provider error [{cacheKey}]", ex); - - if (_options.ThrowIfDistributedCacheError) + /// + /// Gets the specified cacheKey, dataRetriever and expirationRetriever async. + /// + /// The async. + /// Cache key. + /// Data retriever. + /// Expiration retriever. + /// + /// The 1st type parameter. + public async Task> GetAsync(string cacheKey, Func> dataRetriever, Func> expirationRetriever, CancellationToken cancellationToken = default) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + var result = await _localCache.GetAsync( + cacheKey, + async () => { - throw; - } - } - - if (result.HasValue) - { - TimeSpan ts = await GetExpirationAsync(cacheKey, cancellationToken); - - await _localCache.SetAsync(cacheKey, result.Value, ts, cancellationToken); - - return result; - } + var value = default(T); + try + { + value = (await _distributedCache.GetAsync(cacheKey, dataRetriever, expirationRetriever, cancellationToken)).Value; + } + catch (Exception ex) + { + LogMessage($"get async with data retriever from distributed provider error [{cacheKey}]", ex); + if (_options.ThrowIfDistributedCacheError) + throw; + } + return value; + }, + () => GetExpirationAsync(cacheKey, cancellationToken), + cancellationToken); + return result; + } - return CacheValue.NoValue; + /// + /// Gets the specified cacheKey, type, dataRetriever and expirationRetriever async. + /// + /// The async. + /// Cache key. + /// Object Type. + /// Data retriever. + /// Expiration retriever. + /// + public async Task GetAsync(string cacheKey, Type type, Func> dataRetriever, Func> expirationRetriever, CancellationToken cancellationToken = default) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + var result = await _localCache.GetAsync( + cacheKey, + type, + async () => + { + object value = null; + try + { + value = await _distributedCache.GetAsync(cacheKey, type, dataRetriever, expirationRetriever, cancellationToken); + } + catch (Exception ex) + { + LogMessage($"get async with data retriever from distributed provider error [{cacheKey}]", ex); + if (_options.ThrowIfDistributedCacheError) + throw; + } + return value; + }, + () => GetExpirationAsync(cacheKey, cancellationToken), + cancellationToken); + return result; } /// @@ -936,6 +972,29 @@ private TimeSpan GetExpiration(string cacheKey) public async Task GetAsync(string cacheKey, Type type, CancellationToken cancellationToken = default) { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + var result = await _localCache.GetAsync( + cacheKey, + type, + async () => + { + object value = null; + try + { + value = await _distributedCache.GetAsync(cacheKey, type, cancellationToken); + } + catch (Exception ex) + { + LogMessage($"distributed cache get error, [{cacheKey}]", ex); + if (_options.ThrowIfDistributedCacheError) + throw; + } + return value; + }, + () => GetExpirationAsync(cacheKey, cancellationToken), + cancellationToken); + return result; + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); var cacheValue = await _localCache.GetAsync(cacheKey, type, cancellationToken);