237 lines
7.0 KiB
C#
237 lines
7.0 KiB
C#
|
|
using OnlineSalesAutoCrop.CoreAPI.Models;
|
|||
|
|
using OnlineSalesAutoCrop.CoreAPI.Models.Global;
|
|||
|
|
using Microsoft.Extensions.Caching.Distributed;
|
|||
|
|
using Microsoft.Extensions.Caching.Memory;
|
|||
|
|
using Microsoft.Extensions.Options;
|
|||
|
|
using Newtonsoft.Json;
|
|||
|
|
using StackExchange.Redis;
|
|||
|
|
using System;
|
|||
|
|
using System.Collections;
|
|||
|
|
using System.Collections.Concurrent;
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
using System.Linq;
|
|||
|
|
using System.Net;
|
|||
|
|
|
|||
|
|
namespace OnlineSalesAutoCrop.CoreAPI
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// Customized cache, it may be "Distributed Cache (Redis)" or "Memory Cache",
|
|||
|
|
/// it depends on value in appsettings.json file.
|
|||
|
|
/// If the value of "RedisCacheUrl" field is present it will use "Distributed Cache (Redis)."
|
|||
|
|
/// If the value of "SqlServerCacheUrl" field is present it will use "Distributed Cache (Sql Server)"
|
|||
|
|
/// otherwise it will use "Memory Cache".
|
|||
|
|
/// </summary>
|
|||
|
|
public interface IEaseCache : IEnumerable<KeyValuePair<string, object>>
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// Get cached object
|
|||
|
|
/// </summary>
|
|||
|
|
/// <typeparam name="T">Any type of object</typeparam>
|
|||
|
|
/// <param name="key">Key to retrieve data</param>
|
|||
|
|
/// <param name="key2">2nd part of Key to retrieve data</param>
|
|||
|
|
/// <param name="value">Instance of an object type T</param>
|
|||
|
|
/// <returns>True if successful otherwise false</returns>
|
|||
|
|
bool TryGetValue<T>(string key, string key2, out T value);
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Set object to cache
|
|||
|
|
/// </summary>
|
|||
|
|
/// <typeparam name="T">Any type of object</typeparam>
|
|||
|
|
/// <param name="key">Key to set data to cache</param>
|
|||
|
|
/// <param name="key2">2nd part of Key to retrieve data</param>
|
|||
|
|
/// <param name="value">Instance of an object type T</param>
|
|||
|
|
/// <param name="options">How many time to keep in cache</param>
|
|||
|
|
/// <returns>True if successful otherwise false</returns>
|
|||
|
|
bool Set<T>(string key, string key2, T value, DateTimeOffset options);
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Delete caches of matched pattern
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="pattern">Pattern to search</param>
|
|||
|
|
void Clear(string pattern);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
///
|
|||
|
|
/// </summary>
|
|||
|
|
public class EaseCache : IEaseCache
|
|||
|
|
{
|
|||
|
|
private readonly IDatabase _db;
|
|||
|
|
private readonly IServer _server;
|
|||
|
|
private readonly EnumCacheType _cacheType;
|
|||
|
|
private readonly IMemoryCache _memoryCache;
|
|||
|
|
private readonly IDistributedCache _distributedCache;
|
|||
|
|
private readonly ConcurrentBag<string> _distributedKeys = [];
|
|||
|
|
private readonly ConcurrentDictionary<string, ICacheEntry> _cacheEntries = new();
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
///
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="memoryCache"></param>
|
|||
|
|
/// <param name="distributedCache"></param>
|
|||
|
|
/// <param name="settings"></param>
|
|||
|
|
public EaseCache(IMemoryCache memoryCache, IDistributedCache distributedCache, IOptions<AppSettings> settings)
|
|||
|
|
{
|
|||
|
|
CacheConnection cacheCon = new(settings: settings.Value);
|
|||
|
|
|
|||
|
|
_cacheType = cacheCon.CacheType;
|
|||
|
|
if (_cacheType == EnumCacheType.Redis)
|
|||
|
|
{
|
|||
|
|
_db = cacheCon.Connection.GetDatabase();
|
|||
|
|
foreach (EndPoint endpoint in cacheCon.Connection.GetEndPoints(configuredOnly: true))
|
|||
|
|
{
|
|||
|
|
_server = cacheCon.Connection.GetServer(endpoint);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else if (_cacheType == EnumCacheType.SqlServer)
|
|||
|
|
{
|
|||
|
|
_distributedCache = distributedCache;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
_memoryCache = memoryCache;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
///
|
|||
|
|
/// </summary>
|
|||
|
|
/// <typeparam name="T"></typeparam>
|
|||
|
|
/// <param name="key"></param>
|
|||
|
|
/// <param name="key2"></param>
|
|||
|
|
/// <param name="value"></param>
|
|||
|
|
/// <param name="options"></param>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
public bool Set<T>(string key, string key2, T value, DateTimeOffset options)
|
|||
|
|
{
|
|||
|
|
if (!string.IsNullOrEmpty(key2))
|
|||
|
|
key = $"{key}~{key2}";
|
|||
|
|
|
|||
|
|
if (_cacheType == EnumCacheType.Redis)
|
|||
|
|
{
|
|||
|
|
TimeSpan expiryTime = options.DateTime.Subtract(DateTime.Now);
|
|||
|
|
return _db.StringSet(key: key, value: JsonConvert.SerializeObject(value), expiry: expiryTime);
|
|||
|
|
}
|
|||
|
|
else if (_cacheType == EnumCacheType.SqlServer)
|
|||
|
|
{
|
|||
|
|
_distributedKeys.Add(key);
|
|||
|
|
DistributedCacheEntryOptions expiryTime = new() { SlidingExpiration = options.DateTime.Subtract(DateTime.Now) };
|
|||
|
|
_distributedCache.SetString(key: key, value: JsonConvert.SerializeObject(value), options: expiryTime);
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
using ICacheEntry entry = _memoryCache.CreateEntry(key);
|
|||
|
|
entry.RegisterPostEvictionCallback(PostEvictionCallback);
|
|||
|
|
_cacheEntries.AddOrUpdate(key: key, addValue: entry, (o, cacheEntry) =>
|
|||
|
|
{
|
|||
|
|
cacheEntry.Value = entry;
|
|||
|
|
return cacheEntry;
|
|||
|
|
});
|
|||
|
|
entry.AbsoluteExpiration = options;
|
|||
|
|
entry.Value = value;
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
///
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="key"></param>
|
|||
|
|
/// <param name="value"></param>
|
|||
|
|
/// <param name="reason"></param>
|
|||
|
|
/// <param name="state"></param>
|
|||
|
|
private void PostEvictionCallback(object key, object value, EvictionReason reason, object state)
|
|||
|
|
{
|
|||
|
|
if (_cacheType == EnumCacheType.InMemory && reason != EvictionReason.Replaced)
|
|||
|
|
{
|
|||
|
|
_cacheEntries.TryRemove(key: $"{key}", value: out _);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
///
|
|||
|
|
/// </summary>
|
|||
|
|
/// <typeparam name="T"></typeparam>
|
|||
|
|
/// <param name="key"></param>
|
|||
|
|
/// <param name="key2"></param>
|
|||
|
|
/// <param name="value"></param>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
public bool TryGetValue<T>(string key, string key2, out T value)
|
|||
|
|
{
|
|||
|
|
if (!string.IsNullOrEmpty(key2))
|
|||
|
|
key = $"{key}~{key2}";
|
|||
|
|
|
|||
|
|
if (_cacheType == EnumCacheType.Redis)
|
|||
|
|
{
|
|||
|
|
RedisValue keyValue = _db.StringGet(key);
|
|||
|
|
if (string.IsNullOrEmpty(keyValue))
|
|||
|
|
{
|
|||
|
|
value = default;
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
value = JsonConvert.DeserializeObject<T>(keyValue);
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
else if (_cacheType == EnumCacheType.SqlServer)
|
|||
|
|
{
|
|||
|
|
string keyValue = _distributedCache.GetString(key);
|
|||
|
|
if (string.IsNullOrEmpty(keyValue))
|
|||
|
|
{
|
|||
|
|
value = default;
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
value = JsonConvert.DeserializeObject<T>(keyValue);
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
return _memoryCache.TryGetValue(key: key, value: out value);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Delete caches of matched pattern
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="pattern">Pattern to search</param>
|
|||
|
|
public void Clear(string pattern)
|
|||
|
|
{
|
|||
|
|
if (_cacheType == EnumCacheType.Redis)
|
|||
|
|
{
|
|||
|
|
foreach (RedisKey key in _server.Keys(pattern: $"*{pattern}*"))
|
|||
|
|
{
|
|||
|
|
_db.KeyDelete(key: key);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else if (_cacheType == EnumCacheType.SqlServer)
|
|||
|
|
{
|
|||
|
|
foreach (string key in _distributedKeys.Where(key => key.Contains(pattern, StringComparison.OrdinalIgnoreCase)))
|
|||
|
|
{
|
|||
|
|
_distributedCache.Remove(key: key);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
foreach (string key in _cacheEntries.Keys.Where(key => key.Contains(pattern, StringComparison.CurrentCultureIgnoreCase)))
|
|||
|
|
{
|
|||
|
|
_memoryCache.Remove(key: key);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
///
|
|||
|
|
/// </summary>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() => _cacheEntries.Select(pair => new KeyValuePair<string, object>(pair.Key, pair.Value.Value)).GetEnumerator();
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
///
|
|||
|
|
/// </summary>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
|||
|
|
}
|
|||
|
|
}
|