1. 缓存模块源码解析
个人觉得 ABP 分布式缓存模块有三个值得关注的核心点。首先是 AbpRedisCache 类继承了微软原生的 RedisCache,并 通过反射的方式获取RedisCache的私有方法对 RedisCache 进行扩展,实现了 ABP 分布式缓存中的批量操作方法。
为什么要这么做呢?因为基于 Redis 缓存的批量操作需要使用到 StackExchange.Redis 原生的SDK,而且在进行操作前总需要先进行 Redis 连接,而相应的方法和属性原生的 RedisCache 中都有,但是都是私有(private)的,在继承类中也无法使用,所以使用反射的方式提取出相应的方法和属性,以便在继承类中复用。这也是对于类功能进行继承扩展的时候的一种很有用的方式。
第二点是 ABP 缓存模块通过 IDistributedCache
而最常使用的 IDistributedCache
接口,以前其实现类是直接继承 DistributedCache
最后一个是缓存的事务性,通过工作单元使缓存和其他事务操作保持原子性,避免缓存已经更新而其他事务失败回滚导致数据不一致的问题。实际上就是先不真正地更新缓存,而是将缓存数据通过字典保存在工作单元中,保证一个工作单元内拿到的缓存数据是最新的,同时注册工作单元提交事件,在工作单元正在提交成功的时候才执行真正更新缓存的逻辑。
工作单元相关的内容就后面再在专门的章节讲吧,这部分的内容比较复杂,一时半会比较难讲清。
2. 自己扩展的 IDistributedCache
ABP 框架扩展的 IDistributedCache
- 它是基于类的泛型,如果我们在一个服务中需要使用多个缓存类型的话,我们就得注入多个的泛型接口,还是有些不方便的
- 它的缓存键是以默认是以泛型类型的全类名作为前缀,虽然我们可以通过特性指定类型名称,但是对于集合,缓存键就很不清晰了
由于这些情况的存在,也基于我们在日常开发中的使用习惯,我在工作中又基于 ABP 的IDistributedCache
接口进行扩展。内部实现基本一致,主要就是将基于类的泛型,改成基于方法的泛型,并且提供自己的 IDistributedCacheKeyNormalizer 实现类,将缓存键的设置规则交给了缓存存取时进行设置。 代码如下:
public interface IWantDistributedCache
{
///
/// 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.
/// 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.
/// The cache item, or null.
TCacheItem Get<TCacheItem, TCacheKey>(
TCacheKey key,
string cacheName,
bool? hideErrors = null,
bool considerUow = false
) where TCacheItem : class;
///
/// Gets multiple cache items with the given keys.
///
/// The returned list contains exactly the same count of items specified in the given keys.
/// An item in the return list can not be null, but an item in the list has null value
/// if the related key not found in the cache.
///
/// The keys of cached items to be retrieved from the cache.
/// Indicates to throw or hide the exceptions for the distributed cache.
/// 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.
/// List of cache items.
KeyValuePair<TCacheKey, TCacheItem>[] GetMany<TCacheItem, TCacheKey>(
IEnumerable keys,
string cacheName,
bool? hideErrors = null,
bool considerUow = false
) where TCacheItem : class;
///
/// Gets multiple cache items with the given keys.
///
/// The returned list contains exactly the same count of items specified in the given keys.
/// An item in the return list can not be null, but an item in the list has null value
/// if the related key not found in the cache.
///
///
/// The keys of cached items to be retrieved from the cache.
/// Indicates to throw or hide the exceptions for the distributed cache.
/// 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.
/// /// The for the task.
/// List of cache items.
Task[]> GetManyAsync(
IEnumerable keys,
string cacheName,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default
) 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.
/// 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.
/// The for the task.
/// The cache item, or null.
Task<TCacheItem> GetAsync<TCacheItem, TCacheKey>(
[NotNull] TCacheKey key,
string cacheName,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default
) where TCacheItem : class;
///
/// 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.
/// 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.
/// The cache item.
TCacheItem GetOrAdd<TCacheItem, TCacheKey>(
TCacheKey key,
Func factory,
string cacheName,
Func optionsFactory = null,
bool? hideErrors = null,
bool considerUow = false
) where TCacheItem : class;
///
/// 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.
/// 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.
/// The for the task.
/// The cache item.
Task<TCacheItem> GetOrAddAsync<TCacheItem, TCacheKey>(
[NotNull] TCacheKey key,
Func> factory,
string cacheName,
Func optionsFactory = null,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default
) where TCacheItem : class;
///
/// 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.
/// 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.
void Set<TCacheItem, TCacheKey>(
TCacheKey key,
TCacheItem value,
string cacheName,
DistributedCacheEntryOptions options = null,
bool? hideErrors = null,
bool considerUow = false
) where TCacheItem : class;
///
/// 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.
/// 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.
/// The for the task.
/// The indicating that the operation is asynchronous.
Task SetAsync<TCacheItem, TCacheKey>(
[NotNull] TCacheKey key,
[NotNull] TCacheItem value,
string cacheName,
[CanBeNull] DistributedCacheEntryOptions options = null,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default
) where TCacheItem : class;
///
/// Sets multiple cache items.
/// Based on the implementation, this can be more efficient than setting multiple items individually.
///
/// Items to set on the cache
/// The cache options for the value.
/// Indicates to throw or hide the exceptions for the distributed cache.
/// 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.
void SetMany<TCacheItem, TCacheKey>(
IEnumerable> items,
string cacheName,
DistributedCacheEntryOptions options = null,
bool? hideErrors = null,
bool considerUow = false
) where TCacheItem : class;
///
/// Sets multiple cache items.
/// Based on the implementation, this can be more efficient than setting multiple items individually.
///
/// Items to set on the cache
/// The cache options for the value.
/// Indicates to throw or hide the exceptions for the distributed cache.
/// 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.
/// The for the task.
/// The indicating that the operation is asynchronous.
Task SetManyAsync<TCacheItem, TCacheKey>(
IEnumerable> items,
string cacheName,
DistributedCacheEntryOptions options = null,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default
) where TCacheItem : class;
///
/// 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<TCacheKey>(
TCacheKey key,
string cacheName,
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<TCacheKey>(
TCacheKey key,
string cacheName,
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.
/// 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.
void Remove<TCacheItem, TCacheKey>(
TCacheKey key,
string cacheName,
bool? hideErrors = null,
bool considerUow = false
) where TCacheItem : class;
///
/// 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.
/// 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.
/// The for the task.
/// The indicating that the operation is asynchronous.
Task RemoveAsync<TCacheItem, TCacheKey>(
TCacheKey key,
string cacheName,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default
) where TCacheItem : class;
}
public class WantDistributedCache : IWantDistributedCache
{
public const string UowCacheName = "WantDistributedCache";
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 IDistributedCacheKeyNormalizer KeyNormalizer { get; }
protected IHybridServiceScopeFactory ServiceScopeFactory { get; }
protected IUnitOfWorkManager UnitOfWorkManager { get; }
protected SemaphoreSlim SyncSemaphore { get; }
protected DistributedCacheEntryOptions DefaultCacheOptions;
private readonly AbpDistributedCacheOptions _distributedCacheOption;
public WantDistributedCache(
IOptions distributedCacheOption,
IDistributedCache cache,
ICancellationTokenProvider cancellationTokenProvider,
IDistributedCacheSerializer serializer,
IDistributedCacheKeyNormalizer keyNormalizer,
IHybridServiceScopeFactory serviceScopeFactory,
IUnitOfWorkManager unitOfWorkManager )
{
_distributedCacheOption = distributedCacheOption.Value;
Cache = cache;
CancellationTokenProvider = cancellationTokenProvider;
Logger = NullLogger.Instance;
Serializer = serializer;
KeyNormalizer = keyNormalizer;
ServiceScopeFactory = serviceScopeFactory;
UnitOfWorkManager = unitOfWorkManager;
SyncSemaphore = new SemaphoreSlim(1, 1);
SetDefaultOptions();
}
protected virtual string NormalizeKey<TCacheKey>(TCacheKey key, string cacheName)
{
return KeyNormalizer.NormalizeKey(
new DistributedCacheKeyNormalizeArgs(
key.ToString(),
cacheName,
IgnoreMultiTenancy
)
);
}
protected virtual DistributedCacheEntryOptions GetDefaultCacheEntryOptions()
{
foreach (var configure in _distributedCacheOption.CacheConfigurators)
{
var options = configure.Invoke(CacheName);
if (options != null)
{
return options;
}
}
return _distributedCacheOption.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.
///
/// The key of cached item to be retrieved from the cache.
/// Indicates to throw or hide the exceptions for the distributed cache.
/// 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.
/// The cache item, or null.
public virtual TCacheItem Get<TCacheItem, TCacheKey>(
TCacheKey key,
string cacheName,
bool? hideErrors = null,
bool considerUow = false) where TCacheItem : class
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
if (ShouldConsiderUow(considerUow))
{
var value = GetUnitOfWorkCache().GetOrDefault(key)?.GetUnRemovedValueOrNull();
if (value != null)
{
return value;
}
}
byte[] cachedBytes;
try
{
cachedBytes = Cache.Get(NormalizeKey(key, cacheName));
}
catch (Exception ex)
{
if (hideErrors == true)
{
HandleException(ex);
return null;
}
throw;
}
return ToCacheItem(cachedBytes);
}
public virtual KeyValuePair<TCacheKey, TCacheItem>[] GetMany<TCacheItem, TCacheKey>(
IEnumerable keys,
string cacheName,
bool? hideErrors = null,
bool considerUow = false ) where TCacheItem : class
{
var keyArray = keys.ToArray();
var cacheSupportsMultipleItems = Cache as ICacheSupportsMultipleItems;
if (cacheSupportsMultipleItems == null)
{
return GetManyFallback(
keyArray,
cacheName,
hideErrors,
considerUow
);
}
var notCachedKeys = new List();
var cachedValues = new List>();
if (ShouldConsiderUow(considerUow))
{
var uowCache = GetUnitOfWorkCache();
foreach (var key in keyArray)
{
var value = uowCache.GetOrDefault(key)?.GetUnRemovedValueOrNull();
if (value != null)
{
cachedValues.Add(new KeyValuePair(key, value));
}
}
notCachedKeys = keyArray.Except(cachedValues.Select(x => x.Key)).ToList();
if (!notCachedKeys.Any())
{
return cachedValues.ToArray();
}
}
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
byte[][] cachedBytes;
var readKeys = notCachedKeys.Any() ? notCachedKeys.ToArray() : keyArray;
try
{
cachedBytes = cacheSupportsMultipleItems.GetMany(readKeys.Select(key => NormalizeKey(key, cacheName)));
}
catch (Exception ex)
{
if (hideErrors == true)
{
HandleException(ex);
return ToCacheItemsWithDefaultValues(keyArray);
}
throw;
}
return cachedValues.Concat(ToCacheItems(cachedBytes, readKeys)).ToArray();
}
protected virtual KeyValuePair<TCacheKey, TCacheItem>[] GetManyFallback<TCacheItem, TCacheKey>(
TCacheKey[] keys,
string cacheName,
bool? hideErrors = null,
bool considerUow = false) where TCacheItem : class
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
try
{
return keys
.Select(key => new KeyValuePair(
key,
Get(key, cacheName, false, considerUow)
)
).ToArray();
}
catch (Exception ex)
{
if (hideErrors == true)
{
HandleException(ex);
return ToCacheItemsWithDefaultValues(keys);
}
throw;
}
}
public virtual async Task<KeyValuePair<TCacheKey, TCacheItem>[]> GetManyAsync<TCacheItem, TCacheKey>(
IEnumerable keys,
string cacheName,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default ) where TCacheItem : class
{
var keyArray = keys.ToArray();
var cacheSupportsMultipleItems = Cache as ICacheSupportsMultipleItems;
if (cacheSupportsMultipleItems == null)
{
return await GetManyFallbackAsync(
keyArray,
cacheName,
hideErrors,
considerUow,
token
);
}
var notCachedKeys = new List();
var cachedValues = new List>();
if (ShouldConsiderUow(considerUow))
{
var uowCache = GetUnitOfWorkCache();
foreach (var key in keyArray)
{
var value = uowCache.GetOrDefault(key)?.GetUnRemovedValueOrNull();
if (value != null)
{
cachedValues.Add(new KeyValuePair(key, value));
}
}
notCachedKeys = keyArray.Except(cachedValues.Select(x => x.Key)).ToList();
if (!notCachedKeys.Any())
{
return cachedValues.ToArray();
}
}
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
byte[][] cachedBytes;
var readKeys = notCachedKeys.Any() ? notCachedKeys.ToArray() : keyArray;
try
{
cachedBytes = await cacheSupportsMultipleItems.GetManyAsync(
readKeys.Select(key => NormalizeKey(key, cacheName)),
CancellationTokenProvider.FallbackToProvider(token)
);
}
catch (Exception ex)
{
if (hideErrors == true)
{
await HandleExceptionAsync(ex);
return ToCacheItemsWithDefaultValues(keyArray);
}
throw;
}
return cachedValues.Concat(ToCacheItems(cachedBytes, readKeys)).ToArray();
}
protected virtual async Task<KeyValuePair<TCacheKey, TCacheItem>[]> GetManyFallbackAsync<TCacheItem, TCacheKey>(
TCacheKey[] keys,
string cacheName,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default) where TCacheItem : class
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
try
{
var result = new List>();
foreach (var key in keys)
{
result.Add(new KeyValuePair(
key,
await GetAsync(key, cacheName, false, considerUow, token: token))
);
}
return result.ToArray();
}
catch (Exception ex)
{
if (hideErrors == true)
{
await HandleExceptionAsync(ex);
return ToCacheItemsWithDefaultValues(keys);
}
throw;
}
}
///
/// 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.
/// 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.
/// The for the task.
/// The cache item, or null.
public virtual async Task<TCacheItem> GetAsync<TCacheItem, TCacheKey>(
TCacheKey key,
string cacheName,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default) where TCacheItem : class
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
if (ShouldConsiderUow(considerUow))
{
var value = GetUnitOfWorkCache().GetOrDefault(key)?.GetUnRemovedValueOrNull();
if (value != null)
{
return value;
}
}
byte[] cachedBytes;
try
{
cachedBytes = await Cache.GetAsync(
NormalizeKey(key, cacheName),
CancellationTokenProvider.FallbackToProvider(token)
);
}
catch (Exception ex)
{
if (hideErrors == true)
{
await HandleExceptionAsync(ex);
return null;
}
throw;
}
if (cachedBytes == null)
{
return null;
}
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.
///
/// 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.
/// 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.
/// The cache item.
public virtual TCacheItem GetOrAdd<TCacheItem, TCacheKey>(
TCacheKey key,
Func factory,
string cacheName,
Func optionsFactory = null,
bool? hideErrors = null,
bool considerUow = false ) where TCacheItem : class
{
var value = Get(key, cacheName, hideErrors, considerUow);
if (value != null)
{
return value;
}
using (SyncSemaphore.Lock())
{
value = Get(key, cacheName, hideErrors, considerUow);
if (value != null)
{
return value;
}
value = factory();
if (ShouldConsiderUow(considerUow))
{
var uowCache = GetUnitOfWorkCache();
if (uowCache.TryGetValue(key, out var item))
{
item.SetValue(value);
}
else
{
uowCache.Add(key, new UnitOfWorkCacheItem(value));
}
}
Set(key, value, cacheName, optionsFactory?.Invoke(), hideErrors, considerUow);
}
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.
///
/// 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.
/// 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.
/// The for the task.
/// The cache item.
public virtual async Task<TCacheItem> GetOrAddAsync<TCacheItem, TCacheKey>(
TCacheKey key,
Func> factory,
string cacheName,
Func optionsFactory = null,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default ) where TCacheItem : class
{
token = CancellationTokenProvider.FallbackToProvider(token);
var value = await GetAsync(key, cacheName, hideErrors, considerUow, token);
if (value != null)
{
return value;
}
using (await SyncSemaphore.LockAsync(token))
{
value = await GetAsync(key, cacheName, hideErrors, considerUow, token);
if (value != null)
{
return value;
}
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(value));
}
}
await SetAsync(key, value, cacheName, optionsFactory?.Invoke(), hideErrors, considerUow, token);
}
return value;
}
///
/// 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.
/// 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.
public virtual void Set<TCacheItem, TCacheKey>(
TCacheKey key,
TCacheItem value,
string cacheName,
DistributedCacheEntryOptions options = null,
bool? hideErrors = null,
bool considerUow = false) where TCacheItem : class
{
void SetRealCache()
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
try
{
Cache.Set(
NormalizeKey(key, cacheName),
Serializer.Serialize(value),
options ?? DefaultCacheOptions
);
}
catch (Exception ex)
{
if (hideErrors == true)
{
HandleException(ex);
return;
}
throw;
}
}
if (ShouldConsiderUow(considerUow))
{
var uowCache = GetUnitOfWorkCache();
if (uowCache.TryGetValue(key, out _))
{
uowCache[key].SetValue(value);
}
else
{
uowCache.Add(key, new UnitOfWorkCacheItem(value));
}
// ReSharper disable once PossibleNullReferenceException
UnitOfWorkManager.Current.OnCompleted(() =>
{
SetRealCache();
return Task.CompletedTask;
});
}
else
{
SetRealCache();
}
}
///
/// 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.
/// 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.
/// The for the task.
/// The indicating that the operation is asynchronous.
public virtual async Task SetAsync<TCacheItem, TCacheKey>(
TCacheKey key,
TCacheItem value,
string cacheName,
DistributedCacheEntryOptions options = null,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default) where TCacheItem : class
{
async Task SetRealCache()
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
try
{
await Cache.SetAsync(
NormalizeKey(key, cacheName),
Serializer.Serialize(value),
options ?? DefaultCacheOptions,
CancellationTokenProvider.FallbackToProvider(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(value));
}
// ReSharper disable once PossibleNullReferenceException
UnitOfWorkManager.Current.OnCompleted(SetRealCache);
}
else
{
await SetRealCache();
}
}
public void SetMany<TCacheItem, TCacheKey>(
IEnumerable> items,
string cacheName,
DistributedCacheEntryOptions options = null,
bool? hideErrors = null,
bool considerUow = false ) where TCacheItem : class
{
var itemsArray = items.ToArray();
var cacheSupportsMultipleItems = Cache as ICacheSupportsMultipleItems;
if (cacheSupportsMultipleItems == null)
{
SetManyFallback(
itemsArray,
cacheName,
options,
hideErrors,
considerUow
);
return;
}
void SetRealCache()
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
try
{
cacheSupportsMultipleItems.SetMany(
ToRawCacheItems(itemsArray, cacheName),
options ?? DefaultCacheOptions
);
}
catch (Exception ex)
{
if (hideErrors == true)
{
HandleException(ex);
return;
}
throw;
}
}
if (ShouldConsiderUow(considerUow))
{
var uowCache = GetUnitOfWorkCache();
foreach (var pair in itemsArray)
{
if (uowCache.TryGetValue(pair.Key, out _))
{
uowCache[pair.Key].SetValue(pair.Value);
}
else
{
uowCache.Add(pair.Key, new UnitOfWorkCacheItem(pair.Value));
}
}
// ReSharper disable once PossibleNullReferenceException
UnitOfWorkManager.Current.OnCompleted(() =>
{
SetRealCache();
return Task.CompletedTask;
});
}
else
{
SetRealCache();
}
}
protected virtual void SetManyFallback<TCacheItem, TCacheKey>(
KeyValuePair[] items,
string cacheName,
DistributedCacheEntryOptions options = null,
bool? hideErrors = null,
bool considerUow = false ) where TCacheItem : class
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
try
{
foreach (var item in items)
{
Set(
item.Key,
item.Value,
cacheName,
options,
false,
considerUow
);
}
}
catch (Exception ex)
{
if (hideErrors == true)
{
HandleException(ex);
return;
}
throw;
}
}
public virtual async Task SetManyAsync<TCacheItem, TCacheKey>(
IEnumerable> items,
string cacheName,
DistributedCacheEntryOptions options = null,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default ) where TCacheItem : class
{
var itemsArray = items.ToArray();
var cacheSupportsMultipleItems = Cache as ICacheSupportsMultipleItems;
if (cacheSupportsMultipleItems == null)
{
await SetManyFallbackAsync(
itemsArray,
cacheName,
options,
hideErrors,
considerUow,
token
);
return;
}
async Task SetRealCache()
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
try
{
await cacheSupportsMultipleItems.SetManyAsync(
ToRawCacheItems(itemsArray, cacheName),
options ?? DefaultCacheOptions,
CancellationTokenProvider.FallbackToProvider(token)
);
}
catch (Exception ex)
{
if (hideErrors == true)
{
await HandleExceptionAsync(ex);
return;
}
throw;
}
}
if (ShouldConsiderUow(considerUow))
{
var uowCache = GetUnitOfWorkCache();
foreach (var pair in itemsArray)
{
if (uowCache.TryGetValue(pair.Key, out _))
{
uowCache[pair.Key].SetValue(pair.Value);
}
else
{
uowCache.Add(pair.Key, new UnitOfWorkCacheItem(pair.Value));
}
}
// ReSharper disable once PossibleNullReferenceException
UnitOfWorkManager.Current.OnCompleted(SetRealCache);
}
else
{
await SetRealCache();
}
}
protected virtual async Task SetManyFallbackAsync<TCacheItem, TCacheKey>(
KeyValuePair[] items,
string cacheName,
DistributedCacheEntryOptions options = null,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default ) where TCacheItem : class
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
try
{
foreach (var item in items)
{
await SetAsync(
item.Key,
item.Value,
cacheName,
options,
false,
considerUow,
token: token
);
}
}
catch (Exception ex)
{
if (hideErrors == true)
{
await HandleExceptionAsync(ex);
return;
}
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<TCacheKey>(
TCacheKey key,
string cacheName,
bool? hideErrors = null)
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
try
{
Cache.Refresh(NormalizeKey(key, cacheName));
}
catch (Exception ex)
{
if (hideErrors == true)
{
HandleException(ex);
return;
}
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.
/// The for the task.
/// The indicating that the operation is asynchronous.
public virtual async Task RefreshAsync<TCacheKey>(
TCacheKey key,
string cacheName,
bool? hideErrors = null,
CancellationToken token = default)
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
try
{
await Cache.RefreshAsync(NormalizeKey(key, cacheName), CancellationTokenProvider.FallbackToProvider(token));
}
catch (Exception ex)
{
if (hideErrors == true)
{
await HandleExceptionAsync(ex);
return;
}
throw;
}
}
///
/// Removes the cache item for given key from cache.
///
/// The key of cached item to be retrieved from the cache.
/// 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.
/// Indicates to throw or hide the exceptions for the distributed cache.
public virtual void Remove<TCacheItem, TCacheKey>(
TCacheKey key,
string cacheName,
bool? hideErrors = null,
bool considerUow = false) where TCacheItem : class
{
void RemoveRealCache()
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
try
{
Cache.Remove(NormalizeKey(key, cacheName));
}
catch (Exception ex)
{
if (hideErrors == true)
{
HandleException(ex);
return;
}
throw;
}
}
if (ShouldConsiderUow(considerUow))
{
var uowCache = GetUnitOfWorkCache();
if (uowCache.TryGetValue(key, out _))
{
uowCache[key].RemoveValue();
}
// ReSharper disable once PossibleNullReferenceException
UnitOfWorkManager.Current.OnCompleted(() =>
{
RemoveRealCache();
return Task.CompletedTask;
});
}
else
{
RemoveRealCache();
}
}
///
/// 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.
/// 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.
/// The for the task.
/// The indicating that the operation is asynchronous.
public virtual async Task RemoveAsync<TCacheItem, TCacheKey>(
TCacheKey key,
string cacheName,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default) where TCacheItem : class
{
async Task RemoveRealCache()
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
try
{
await Cache.RemoveAsync(NormalizeKey(key, cacheName), CancellationTokenProvider.FallbackToProvider(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].RemoveValue();
}
// ReSharper disable once PossibleNullReferenceException
UnitOfWorkManager.Current.OnCompleted(RemoveRealCache);
}
else
{
await RemoveRealCache();
}
}
protected virtual void HandleException(Exception ex)
{
AsyncHelper.RunSync(() => HandleExceptionAsync(ex));
}
protected virtual async Task HandleExceptionAsync(Exception ex)
{
Logger.LogException(ex, LogLevel.Warning);
using (var scope = ServiceScopeFactory.CreateScope())
{
await scope.ServiceProvider
.GetRequiredService()
.NotifyAsync(new ExceptionNotificationContext(ex, LogLevel.Warning));
}
}
protected virtual KeyValuePair<TCacheKey, TCacheItem>[] ToCacheItems<TCacheItem, TCacheKey>(byte[][] itemBytes, TCacheKey[] itemKeys) where TCacheItem: class
{
if (itemBytes.Length != itemKeys.Length)
{
throw new AbpException("count of the item bytes should be same with the count of the given keys");
}
var result = new List>();
for (int i = 0; i < itemKeys.Length; i++)
{
result.Add(
new KeyValuePair(
itemKeys[i],
ToCacheItem(itemBytes[i])
)
);
}
return result.ToArray();
}
[CanBeNull]
protected virtual TCacheItem ToCacheItem<TCacheItem>([CanBeNull] byte[] bytes) where TCacheItem : class
{
if (bytes == null)
{
return null;
}
return Serializer.Deserialize(bytes);
}
protected virtual KeyValuePair<string, byte[]>[] ToRawCacheItems<TCacheItem, TCacheKey>(KeyValuePair[] items, string cacheName )
{
return items
.Select(i => new KeyValuePair<string, byte[]>(
NormalizeKey(i.Key, cacheName),
Serializer.Serialize(i.Value)
)
).ToArray();
}
private static KeyValuePair<TCacheKey, TCacheItem>[] ToCacheItemsWithDefaultValues<TCacheItem, TCacheKey>(TCacheKey[] keys)
{
return keys
.Select(key => new KeyValuePair(key, default))
.ToArray();
}
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<TCacheItem, TCacheKey>() where TCacheItem : class
{
if (UnitOfWorkManager.Current == null)
{
throw new AbpException($"There is no active UOW.");
}
return UnitOfWorkManager.Current.GetOrAddItem(GetUnitOfWorkCacheKey(),
key => new Dictionary>());
}
}
[Dependency(ReplaceServices = true)]
public class WantDistributedCacheKeyNormalizer : IDistributedCacheKeyNormalizer, ITransientDependency
{
protected AbpDistributedCacheOptions DistributedCacheOptions { get; }
public SuncereDistributedCacheKeyNormalizer(
IOptions distributedCacheOptions )
{
DistributedCacheOptions = distributedCacheOptions.Value;
}
public virtual string NormalizeKey(DistributedCacheKeyNormalizeArgs args)
{
// 缓存格式: a:appname,c:cachename,k:key
var normalizedKey = $"a:{DistributedCacheOptions.KeyPrefix},c:{args.CacheName},k:{args.Key}";
return normalizedKey;
}
}
[DependsOn(typeof(AbpCachingModule))]
public class WantAbpCacheModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
//注入缓存类
context.Services.AddSingleton(typeof(ISuncereDistributedCache), typeof(SuncereDistributedCache));
}
}
参考文章:
ABP 官方文档 - 缓存
ABP 系列总结:
目录:ABP 系列总结
上一篇:ABP - 缓存模块(1)
下一篇:ABP - 本地事件总线