Equinox.Redis 0.1.0

The owner has unlisted this package. This could mean that the package is deprecated, has security vulnerabilities or shouldn't be used anymore.
dotnet add package Equinox.Redis --version 0.1.0
                    
NuGet\Install-Package Equinox.Redis -Version 0.1.0
                    
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="Equinox.Redis" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Equinox.Redis" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="Equinox.Redis" />
                    
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 Equinox.Redis --version 0.1.0
                    
#r "nuget: Equinox.Redis, 0.1.0"
                    
#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 Equinox.Redis@0.1.0
                    
#: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=Equinox.Redis&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=Equinox.Redis&version=0.1.0
                    
Install as a Cake Tool

Equinox.Redis

A high-performance Redis client for .NET built with official Microsoft libraries only. Targets .NET Standard 2.0 (.NET Framework 4.6.1+) and .NET Standard 2.1 (modern .NET).

Features

  • High-level abstractions — Cache, Counter, Queue, Stack, Set, SortedSet, HashMap, and distributed Lock with JSON/POCO serialization
  • RESP2 / RESP3 — Automatic RESP3 negotiation with transparent RESP2 fallback
  • Bitmaps, HyperLogLog, Geo — Full command coverage for bit ops, cardinality, and geospatial queries
  • Connection pooling — Hybrid multiplexing for concurrent commands, exclusive sockets for blocking/pipelines/transactions, idle trim, and per-node clustermaxpoolsize
  • Allocation-conscious — Pooled command arg arrays on hot APIs, buffered RESP I/O, and firehose multiplex writes
  • Standalone, Cluster, and Sentinel — Connection strings for all three topologies
  • Pipelines & transactions — Batched commands and MULTI/EXEC with connection affinity
  • Pub/Sub — Subscribe, pattern subscribe, and publish
  • Async throughout — Cancellation tokens on I/O paths
  • SSL/TLS — Secure connections with certificate options
  • No third-party Redis SDKs — Only official Microsoft packages (plus System.Text.Json)

Installation

dotnet add package Equinox.Redis

Quick Start

using Equinox.Redis;
using Equinox.Redis.Abstractions;

await using var redis = await RedisClient.ConnectAsync("host=localhost;port=6379");

// Prefer abstractions for application code
var cache = new RedisCache(redis, keyPrefix: "app");
await cache.SetAsync("user:1", new { Name = "Ada" }, TimeSpan.FromMinutes(10));
var user = await cache.GetAsync<UserDto>("user:1");

Connection options:

// Connection string
await using var redis = new RedisClient("host=localhost;port=6379;password=secret;maxpoolsize=20");

// Settings object
await using var redis2 = new RedisClient(new ConnectionSettings
{
    Host = "localhost",
    Port = 6379,
    Password = "secret",
    MaxPoolSize = 20
});

// Cluster / Sentinel
await using var cluster = new RedisClient("cluster://127.0.0.1:17000,127.0.0.1:17001,127.0.0.1:17002");
await using var sentinel = new RedisClient("sentinel://127.0.0.1:26379,127.0.0.1:26380/mymaster");

Abstractions APIs

All abstractions live in Equinox.Redis.Abstractions. Construct them with a RedisClient and a key (or key prefix). Values are serialized with System.Text.Json unless noted.

Cache (RedisCache)

Typed get/set with optional TTL and get-or-set.

var cache = new RedisCache(redis, keyPrefix: "cache");

await cache.SetAsync("session:abc", sessionDto, TimeSpan.FromMinutes(30));
var session = await cache.GetAsync<SessionDto>("session:abc");

var profile = await cache.GetOrSetAsync("profile:42", async () =>
{
    return await LoadProfileFromDbAsync(42);
}, TimeSpan.FromHours(1));

await cache.ExistsAsync("session:abc");
await cache.RemoveAsync("session:abc");
await cache.SetExpiryAsync("profile:42", TimeSpan.FromMinutes(5));
var ttl = await cache.GetTimeToLiveAsync("profile:42");

Counter (RedisCounter)

Atomic counters with optional expiry.

var counters = new RedisCounter(redis, keyPrefix: "metrics");

await counters.IncrementAsync("pageviews");
await counters.IncrementByAsync("pageviews", 10);
await counters.DecrementAsync("inventory:sku-1");

long value = await counters.GetAsync("pageviews");
await counters.SetAsync("pageviews", 1000);
await counters.ResetAsync("pageviews");
await counters.SetExpiryAsync("pageviews", TimeSpan.FromDays(1));

Queue (RedisQueue<T>) — FIFO

var jobs = new RedisQueue<JobMessage>(redis, key: "queue:jobs");

await jobs.EnqueueAsync(new JobMessage { Id = "j1", Type = "email" });
JobMessage? next = await jobs.DequeueAsync();
JobMessage? peek = await jobs.PeekAsync();

long length = await jobs.LengthAsync();
JobMessage?[] all = await jobs.GetAllAsync();
await jobs.ClearAsync();

Stack (RedisStack<T>) — LIFO

var undo = new RedisStack<EditOp>(redis, key: "stack:undo:doc-9");

await undo.PushAsync(new EditOp { Action = "insert" });
EditOp? last = await undo.PopAsync();
EditOp? top = await undo.PeekAsync();
long count = await undo.CountAsync();

Set (RedisSet<T>)

var tags = new RedisSet<string>(redis, key: "set:post:12:tags");

await tags.AddAsync("redis");
await tags.AddAsync("dotnet");
bool has = await tags.ContainsAsync("redis");
string? random = await tags.GetRandomAsync();
string? popped = await tags.PopAsync();

string?[] members = await tags.GetAllAsync();
await tags.RemoveAsync("dotnet");
await tags.ClearAsync();

Sorted set (RedisSortedSet<T>)

Leaderboards and ranked collections.

var scores = new RedisSortedSet<string>(redis, key: "zset:leaderboard");

await scores.AddAsync("alice", 1200);
await scores.AddAsync("bob", 950);
await scores.IncrementScoreAsync("bob", 100);

double? aliceScore = await scores.GetScoreAsync("alice");
long? rank = await scores.GetRankAsync("alice");          // low → high
long? revRank = await scores.GetReverseRankAsync("alice"); // high → low

string?[] top3 = await scores.GetTopAsync(3);
string?[] bottom3 = await scores.GetBottomAsync(3);
string?[] byRank = await scores.GetRangeByRankAsync(0, 9);
string?[] byScore = await scores.GetRangeByScoreAsync(1000, 2000);

Hash map (RedisHashMap)

Field access plus POCO mapping (nested properties flatten with :). Use [RedisField("name")] and [RedisIgnore] to control mapping.

var userHash = new RedisHashMap(redis, key: "hash:user:1");

await userHash.SetFieldAsync("email", "ada@example.com");
await userHash.SetFieldsAsync(new Dictionary<string, string>
{
    ["name"] = "Ada",
    ["role"] = "admin"
});

string? email = await userHash.GetFieldAsync("email");
await userHash.IncrementFieldAsync("loginCount");

// POCO round-trip
await userHash.SetObjectAsync(new User
{
    Name = "Ada",
    Email = "ada@example.com",
    Address = new Address { City = "London" } // stored as Address:City
});
User? loaded = await userHash.GetObjectAsync<User>();

// Patch only changed fields
var updated = await userHash.GetObjectAsync<User>();
updated!.Email = "new@example.com";
await userHash.UpdateFieldsAsync(loaded, updated);
public sealed class User
{
    public string Name { get; set; } = "";

    [RedisField("email_addr")]
    public string Email { get; set; } = "";

    [RedisIgnore]
    public string? Scratch { get; set; }

    public Address? Address { get; set; }
}

Distributed lock (RedisLock)

SET NX + PX acquire; safe release with an ownership token (Lua).

var locker = new RedisLock(redis, key: "lock:invoice:42", timeout: TimeSpan.FromSeconds(30));

if (await locker.AcquireAsync())
{
    try
    {
        await ProcessInvoiceAsync(42);
        await locker.ExtendAsync(TimeSpan.FromSeconds(30));
    }
    finally
    {
        await locker.ReleaseAsync();
    }
}

// Retry until acquired or wait timeout elapses
if (await locker.TryAcquireAsync(TimeSpan.FromSeconds(5)))
{
    await DoWorkAsync();
    await locker.ReleaseAsync();
}

bool held = await locker.IsHeldAsync();

Scoped pattern (acquire + auto-release):

var locker = new RedisLock(redis, "lock:resource", TimeSpan.FromSeconds(15));
await using (await locker.AcquireScopedAsync())
{
    await DoWorkAsync();
}

Command API (lower level)

When you need raw Redis commands, use the typed command surfaces on RedisClient:

await redis.Strings.SetAsync("key", "value", TimeSpan.FromMinutes(5));
var value = await redis.Strings.GetAsync("key");

await redis.Lists.RPushAsync("list", "a", "b");
await redis.Sets.SAddAsync("set", "x", "y");
await redis.Hashes.HSetAsync("hash", "f", "v");
await redis.SortedSets.ZAddAsync("zset", (1.0, "one"));
await redis.Streams.XAddAsync("stream", new Dictionary<string, string> { ["f"] = "v" });
await redis.Bitmaps.SetBitAsync("flags", 7, true);
await redis.HyperLogLog.AddAsync("visitors", "user:1", "user:2");
await redis.Geo.AddAsync("cities", new GeoEntry(13.36, 38.11, "Palermo"));
await redis.Keys.ExpireAsync("key", TimeSpan.FromHours(1));
await redis.PingAsync();

Pipelines, transactions, pub/sub

By default, non-blocking commands share multiplexed socket(s) with a firehose writer (batch writes + flush) so Task.WhenAll scales without one connection per call. Raise multiplexconnectioncount under heavy write contention (default is 2). Blocking and connection-local commands (BLPOP, CLIENT, MULTI, …) use an exclusive pooled connection so they do not stall or desync other traffic. Pipelines still borrow one exclusive connection and write all commands before reading replies (order preserved). Prefer CreatePipeline() when you want many commands in a single RTT on one connection. Pub/sub subscribers use a dedicated connection outside the command pool.

var pipeline = redis.CreatePipeline();
pipeline.Queue("SET", "a", "1");
pipeline.Queue("SET", "b", "2");
pipeline.Queue("GET", "a");
var results = await pipeline.ExecuteAsync();

await using var tx = await redis.BeginTransactionAsync();
tx.Queue("INCR", "counter");
tx.Queue("GET", "counter");
var exec = await tx.ExecAsync();

await redis.PubSub.PublishAsync("channel", "hello");
await using var sub = await redis.PubSub.CreateSubscriberAsync();
sub.MessageReceived += (_, e) => Console.WriteLine(e.Message);
await sub.SubscribeAsync("channel");
var result = await redis.ExecuteRawAsync("CLIENT", new[] { "LIST" });

Connection String Options

Option Description Default
host, server Redis server hostname localhost
port Redis server port 6379
password, pwd Authentication password null
username, user Authentication username (Redis 6+) null
database, db Database index 0
maxpoolsize, max Maximum pool connections (standalone/sentinel) 10
minpoolsize, min Minimum pool connections (idle trim floor) 1
clustermaxpoolsize Maximum connections per cluster node pool 4
idleconnectiontimeout Close idle pooled connections above min (ms); 0 disables 60000
enablemultiplexing, multiplexing Share sockets for non-blocking commands true
multiplexconnectioncount, multiplexconnections Number of shared multiplex sockets 2
connecttimeout, timeout Connection timeout (ms) 5000
commandtimeout Command timeout (ms) 30000
ssl, tls Enable SSL/TLS false
sslhost SSL certificate host null
clientname, name Client connection name null
autoreconnect Auto-reconnect on failure true
protocol auto (RESP3 then RESP2), resp2, or resp3 auto

URL forms: cluster://host:port,... and sentinel://host:port,.../masterName.

Building

dotnet build

Testing

# Full local E2E (standalone + cluster + sentinel via Podman)
./scripts/run-all-e2e-tests.sh --keep

# Standalone only (defaults to localhost:6380)
./scripts/run-e2e-tests.sh --keep
REDIS_PORT=6380 dotnet test

License

MIT License

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  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. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 is compatible. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos 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