Santel.Redis.TypedKeys 1.0.1

There is a newer version of this package available.
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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Santel.Redis.TypedKeys" Version="1.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Santel.Redis.TypedKeys" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="Santel.Redis.TypedKeys" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Santel.Redis.TypedKeys --version 1.0.1
                    
#r "nuget: Santel.Redis.TypedKeys, 1.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Santel.Redis.TypedKeys@1.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Santel.Redis.TypedKeys&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=Santel.Redis.TypedKeys&version=1.0.1
                    
Install as a Cake Tool

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 prefix is null or empty: key name = PropertyName.
  • Else: key name = ${prefix}_{PropertyName}. Publish channel: the raw prefix literal (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>: KeyName
  • RedisHashKey<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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.1 197 11/8/2025
1.1.0 219 11/4/2025
1.0.4 166 10/26/2025
1.0.3 194 10/15/2025
1.0.2 206 10/15/2025
1.0.1 136 10/4/2025
1.0.0 137 10/4/2025