• ABP - 缓存模块(2)


    1. 缓存模块源码解析

    个人觉得 ABP 分布式缓存模块有三个值得关注的核心点。首先是 AbpRedisCache 类继承了微软原生的 RedisCache,并 通过反射的方式获取RedisCache的私有方法对 RedisCache 进行扩展,实现了 ABP 分布式缓存中的批量操作方法。

    image

    为什么要这么做呢?因为基于 Redis 缓存的批量操作需要使用到 StackExchange.Redis 原生的SDK,而且在进行操作前总需要先进行 Redis 连接,而相应的方法和属性原生的 RedisCache 中都有,但是都是私有(private)的,在继承类中也无法使用,所以使用反射的方式提取出相应的方法和属性,以便在继承类中复用。这也是对于类功能进行继承扩展的时候的一种很有用的方式。

    image

    image

    第二点是 ABP 缓存模块通过 IDistributedCache 接口扩展了原生的 IDistributedCache 接口的功能,而在具体实现上是将原有的 IDistributedCache 服务注入进行复用相应功能的,并在前后增加额外的逻辑对功能进行扩展增强,实际上就是适配器模式。

    image

    image

    而最常使用的 IDistributedCache 接口,以前其实现类是直接继承 DistributedCache 的,现在改成了直接用适配器模式。

    image

    最后一个是缓存的事务性,通过工作单元使缓存和其他事务操作保持原子性,避免缓存已经更新而其他事务失败回滚导致数据不一致的问题。实际上就是先不真正地更新缓存,而是将缓存数据通过字典保存在工作单元中,保证一个工作单元内拿到的缓存数据是最新的,同时注册工作单元提交事件,在工作单元正在提交成功的时候才执行真正更新缓存的逻辑。

    image

    工作单元相关的内容就后面再在专门的章节讲吧,这部分的内容比较复杂,一时半会比较难讲清。

    2. 自己扩展的 IDistributedCache

    ABP 框架扩展的 IDistributedCache 泛型接口在内部帮我们处理实例对象进行序列化/反序列化以及转码为 byte 数组的问题,大大提升了我们使用分布式缓存的方便性,但也存在一些问题。

    • 它是基于类的泛型,如果我们在一个服务中需要使用多个缓存类型的话,我们就得注入多个的泛型接口,还是有些不方便的
    • 它的缓存键是以默认是以泛型类型的全类名作为前缀,虽然我们可以通过特性指定类型名称,但是对于集合,缓存键就很不清晰了
      由于这些情况的存在,也基于我们在日常开发中的使用习惯,我在工作中又基于 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 - 本地事件总线

  • 相关阅读:
    MySQL中对于索引的理解
    c语言map的详细用法
    基于pytorch的BP神经网络实现
    ​2:DDD概念大白话
    SpringBoot SSE服务端主动推送事件详解
    头条文章_signature
    C++入门——域作用符,命名空间的讲解
    第七章 查找 七、红黑树
    MySQL 高级(进阶) SQL 语句 (一)
    Ubuntu 22.04 无法使用网易云音乐
  • 原文地址:https://www.cnblogs.com/wewant/p/17135175.html