Skip to content

Instantly share code, notes, and snippets.

@IntranetFactory
Created April 27, 2024 10:13
Show Gist options
  • Save IntranetFactory/09338b44aa3c6ad7b2cab0284acfe14c to your computer and use it in GitHub Desktop.
Save IntranetFactory/09338b44aa3c6ad7b2cab0284acfe14c to your computer and use it in GitHub Desktop.
EFCoreSecondLevelCacheInterceptor Distributed cached provider using Redis sentinel
using adenin.Platform.Configuration;
using EFCoreSecondLevelCacheInterceptor;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using StackExchange.Redis;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
namespace adenin.Platform.Caching;
// ReSharper disable once InconsistentNaming
public class EFRedisCacheProvider : IEFCacheServiceProvider
{
public const string CacheName = "PlatformEFFusionCache";
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<EFRedisCacheProvider> _redisCacheProviderLogger;
private readonly ConcurrentDictionary<string, ConnectionMultiplexer> _redisConnections = new(StringComparer.Ordinal);
public EFRedisCacheProvider(
IOptions<EFCoreSecondLevelCacheSettings> cacheSettings,
IServiceProvider serviceProvider,
ILogger<EFRedisCacheProvider> redisCacheProviderLogger)
{
ArgumentNullException.ThrowIfNull(cacheSettings);
ArgumentNullException.ThrowIfNull(serviceProvider);
_serviceProvider = serviceProvider;
_redisCacheProviderLogger = redisCacheProviderLogger;
}
public void InsertValue(EFCacheKey cacheKey, [CanBeNull] EFCachedData value, EFCachePolicy cachePolicy)
{
ArgumentNullException.ThrowIfNull(cacheKey);
ArgumentNullException.ThrowIfNull(cachePolicy);
value ??= new EFCachedData { IsNull = true };
var redisDb = GetRedisConnection().GetDatabase();
var keyHash = cacheKey.KeyHash;
foreach (var rootCacheKey in cacheKey.CacheDependencies)
{
if (string.IsNullOrWhiteSpace(rootCacheKey))
{
continue;
}
var expiryTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + cachePolicy.CacheTimeout.TotalMilliseconds;
redisDb.SortedSetAdd(SortedSetCacheKey(rootCacheKey), keyHash, expiryTime);
}
var data = Serialize(value);
redisDb.StringSet(keyHash, data, cachePolicy.CacheTimeout);
}
public void ClearAllCachedEntries()
{
_redisCacheProviderLogger.LogWarning("EFFusionCacheProvider.ClearAllCachedEntries was called");
}
public EFCachedData? GetValue(EFCacheKey cacheKey, EFCachePolicy cachePolicy)
{
ArgumentNullException.ThrowIfNull(cacheKey);
var redisDb = GetRedisConnection().GetDatabase();
var maybeValue = redisDb.StringGet(cacheKey.KeyHash);
return maybeValue.HasValue ? Deserialize<EFCachedData>(maybeValue) : null;
}
public void InvalidateCacheDependencies(EFCacheKey cacheKey)
{
ArgumentNullException.ThrowIfNull(cacheKey);
var redisDb = GetRedisConnection().GetDatabase();
foreach (var rootCacheKey in cacheKey.CacheDependencies)
{
if (string.IsNullOrWhiteSpace(rootCacheKey))
{
continue;
}
var setKey = SortedSetCacheKey(rootCacheKey);
var dependencyKeys = new HashSet<string>();
foreach (var item in redisDb.SortedSetScan(setKey))
{
_ = dependencyKeys.Add(item.Element);
}
if (dependencyKeys.Count > 0)
{
redisDb.KeyDelete([.. dependencyKeys]);
}
redisDb.KeyDelete(setKey);
}
}
private ConnectionMultiplexer GetRedisConnection()
{
return _redisConnections.GetOrAdd("redis", _ =>
{
var config = _serviceProvider.GetRequiredService<IWebHostEnvironment>();
var redisSetting = config.GetAppConfiguration().GetValue<string>("RedisConnectionString");
var hosts = redisSetting.Split(',');
if (hosts.Length <= 1)
{
return ConnectionMultiplexer.Connect(redisSetting);
}
var endpoints = new EndPointCollection();
foreach (var host in hosts)
{
endpoints.Add(host, 26379);
}
var redisOptions = new ConfigurationOptions
{
EndPoints = endpoints,
ServiceName = "mymaster",
TieBreaker = ""
};
return ConnectionMultiplexer.Connect(redisOptions);
});
}
private static string SortedSetCacheKey(string rootCacheKey) => $"{rootCacheKey}:Sets";
private static string Serialize<T>(T? obj)
{
return JsonConvert.SerializeObject(obj);
}
private static T? Deserialize<T>(string data)
{
return JsonConvert.DeserializeObject<T>(data);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment