Santel.Redis.TypedKeys
1.0.1
See the version list below for details.
dotnet add package Santel.Redis.TypedKeys --version 1.0.1
NuGet\Install-Package Santel.Redis.TypedKeys -Version 1.0.1
<PackageReference Include="Santel.Redis.TypedKeys" Version="1.0.1" />
<PackageVersion Include="Santel.Redis.TypedKeys" Version="1.0.1" />
<PackageReference Include="Santel.Redis.TypedKeys" />
paket add Santel.Redis.TypedKeys --version 1.0.1
#r "nuget: Santel.Redis.TypedKeys, 1.0.1"
#:package Santel.Redis.TypedKeys@1.0.1
#addin nuget:?package=Santel.Redis.TypedKeys&version=1.0.1
#tool nuget:?package=Santel.Redis.TypedKeys&version=1.0.1
Santel.Redis.TypedKeys
Strongly-typed Redis key & hash abstractions (string key + hash field semantics) with optional in‑memory caching, structured metadata, and lightweight pub/sub notifications. Built on StackExchange.Redis targeting .NET 9.
Why
Typical Redis usage scatters string constants, serialization logic, caching flags, and pub/sub plumbing across the codebase. This library:
- Centralizes key definitions in a single derived context.
- Auto‑wires all declared
RedisKey<T>/RedisHashKey<T>properties via reflection. - Adds optional per-key / per-field in-memory caching.
- Adds lightweight publish notifications (single field or bulk) for cache invalidation in other processes.
- Wraps stored payloads with timestamp + Persian date metadata (
RedisDataWrapper<T>). - Provides bulk operations, pagination helpers, concurrency helpers, size inspection, and basic safety limits.
Package Installation
dotnet add package Santel.Redis.TypedKeys
Or add to a project file:
<ItemGroup>
<PackageReference Include="Santel.Redis.TypedKeys" Version="1.0.1" />
</ItemGroup>
Core Types Overview
| Type | Summary |
|---|---|
RedisDBContextModule |
Base class you inherit; discovers and initializes key/hash properties. |
RedisKey<T> |
Single Redis string key abstraction (value + metadata + optional cache). |
RedisHashKey<T> |
Redis hash abstraction with per-field cache & bulk helpers. |
RedisDataWrapper<T> |
Metadata wrapper (UTC DateTime, Persian formatted string, Data). |
IRedisCommonKeyMethods / IRedisCommonHashKeyMethods |
Internal capability contracts. |
Constructor & Key Naming
RedisDBContextModule constructor (current signature):
public RedisDBContextModule(
IConnectionMultiplexer connectionMultiplexerWrite,
IConnectionMultiplexer connectionMultiplexerRead,
bool keepDataInMemory,
ILogger logger,
bool usePushNotification = true,
string? prefix = null)
Key naming rule:
- If
prefixis null or empty: key name =PropertyName. - Else: key name =
${prefix}_{PropertyName}. Publish channel: the rawprefixliteral (can be empty; supply a non-empty isolation token in multi-env scenarios).
Defining a Context
public class AppRedisContext : RedisDBContextModule
{
// Database 0 simple key
public RedisKey<string> AppVersion { get; set; } = new(0);
// Database 1: hash of user profiles
public RedisHashKey<UserProfile> Users { get; set; } = new(1);
// Database 2: custom serialization example
public RedisHashKey<Invoice> Invoices { get; set; } = new(2,
serialize: inv => JsonConvert.SerializeObject(inv, Formatting.None),
deSerialize: s => JsonConvert.DeserializeObject<Invoice>(s)!);
public AppRedisContext(IConnectionMultiplexer writer,
IConnectionMultiplexer reader,
ILogger<AppRedisContext> logger,
string? prefix,
bool keepDataInMemory = true)
: base(writer, reader, keepDataInMemory, logger, usePushNotification: true, prefix: prefix) { }
}
public record UserProfile(int Id, string Name)
{
public UserProfile() : this(0, string.Empty) { }
}
public record Invoice(string Id, decimal Amount);
Basic Usage
var ctx = new AppRedisContext(writerMux, readerMux, logger, prefix: "Prod", keepDataInMemory: true);
// String key
ctx.AppVersion.Write("1.4.9");
var ver = ctx.AppVersion.Read();
// Hash single entry
ctx.Users.Write("42", new UserProfile(42, "Alice"));
var alice = ctx.Users.Read("42");
// Bulk hash write
await ctx.Users.WriteAsync(new Dictionary<string, UserProfile>
{
["1"] = new(1, "Bob"),
["2"] = new(2, "Carol")
}, forceToPublish: true); // triggers channel publish 'Users|all'
// Bulk read (cached after first fetch if keepDataInMemory=true)
var some = ctx.Users.Read(new [] { "1", "2" });
// Async field read
var carol = await ctx.Users.ReadAsync("2");
Pub/Sub Model
Channel name = provided prefix (if empty you effectively broadcast on an empty channel – usually supply something like env or tenant id).
Messages:
RedisKey<T>:KeyNameRedisHashKey<T>single field update:HashName|{field}RedisHashKey<T>bulk/forced publish:HashName|all
Subscriber example:
var sub = readerMux.GetSubscriber();
await sub.SubscribeAsync("Prod", (channel, msg) =>
{
// Patterns: Users|123 Users|all AppVersion
var text = (string)msg;
if (text.EndsWith("|all"))
{
// invalidate all cached fields for that hash locally
}
else if (text.Contains('|'))
{
var parts = text.Split('|'); // parts[0]=hash, parts[1]=field
}
else
{
// simple key changed
}
});
Caching & Invalidation
Enable by passing keepDataInMemory: true to the base constructor.
RedisKey<T>: last value wrapper cached.RedisHashKey<T>: individual field wrappers cached lazily. Invalidation helpers:
ctx.AppVersion.ForceToReFetch(); // drop single key cache
ctx.Users.ForceToReFetch("42"); // drop one field
ctx.Users.ForceToReFetchAll(); // drop all cached fields
ctx.Users.DoPublishAll(); // manual global publish (Users|all)
A pub/sub handler in other processes should call ForceToReFetch / ForceToReFetchAll accordingly.
Paging Hash Fields
var (fieldNames, total) = await ctx.GetHashKeysByPage(
database: 1,
hashKey: ctx.Users.FullName, // underlying redis key
pageNumber: 2,
pageSize: 25);
Uses cursor-like offset logic with HashScanAsync.
Memory Usage / Size
long sizeKey = ctx.AppVersion.GetSize();
long sizeUsers = ctx.Users.GetSize();
Uses MEMORY USAGE – may return 0 if unsupported by server or lacking permission.
Hash Length Safety Limit
RedisHashKey<T> enforces a soft limit (4000 fields). Bulk or single writes failing the limit log an informational message and return false. Adjust in source (IsLimitExceeded).
Bulk Write Chunking
WriteAsync(IDictionary<string,T>, maxChunkSizeInBytes) splits large payloads by serialized byte length to avoid over-large single operations.
Custom Serialization
You may override serialization per key / hash (shown earlier for Invoices). Metadata wrapping is preserved.
Metadata Wrapper
All stored data is nested inside RedisDataWrapper<T>:
{
"Data": { /* your T */ },
"DateTime": "2025-01-01T10:12:33.456Z",
"PersianLastUpdate": "1403/10/11 13:42"
}
Access with ReadFull / ReadFull(string key) when you need timestamps.
Removing Data
await ctx.Users.RemoveAsync("42"); // remove field
await ctx.Users.RemoveAsync(new RedisValue[]{"1","2"}); // multi-field
await ctx.Users.RemoveAsync(); // delete whole hash key
Concurrency Test Utility
await RedisHashKey<int>.TestConcurrency_IN_ForceToReFetchAll(ctx.SomeIntHash);
Stress test for simultaneous cache invalidation + reads.
DI Registration Sketch
services.AddSingleton<IConnectionMultiplexer>(sp => ConnectionMultiplexer.Connect(config));
services.AddSingleton<AppRedisContext>(sp =>
{
var mux = sp.GetRequiredService<IConnectionMultiplexer>();
var logger = sp.GetRequiredService<ILogger<AppRedisContext>>();
return new AppRedisContext(mux, mux, logger, prefix: Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"));
});
(You may supply separate read/write multiplexers if desired.)
Error Handling & Logging
All operations catch and log exceptions with contextual key info using the provided ILogger.
Versioning / Roadmap Ideas
Planned / potential improvements:
- Configurable hash size limit.
- Optional compression (e.g., LZ4) layer.
- Structured pub/sub event model.
- Metrics hooks (latency / miss ratio).
License
MIT
Disclaimer
Use responsibly in high cardinality scenarios: the in-memory cache is per-process and unbounded except for the hash field count guard.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net9.0
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.0)
- Newtonsoft.Json (>= 13.0.3)
- StackExchange.Redis (>= 2.7.33)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.