Strings and keys
Frequent operations are available directly on IRespireClient; complete string and key surfaces live under Strings and Keys.
Read and write
await redis.SetAsync("greeting", "hello");
string? greeting = await redis.GetStringAsync("greeting");
await redis.SetAsync("visits", 1);
long visits = await redis.IncrementAsync("visits");
Conditional writes use SetWhen:
bool created = await redis.SetAsync(
"lock:invoice:42",
requestId,
expiry: TimeSpan.FromSeconds(20),
when: SetWhen.NotExists);
Expiry
RespireExpiry is the single expiry argument: nothing, a relative TTL, an absolute instant, or "keep the TTL the key already has". A TimeSpan or DateTimeOffset converts implicitly.
string token = "session-token";
await redis.SetAsync("session:42", token); // no TTL (clears any existing one)
await redis.SetAsync("session:42", token, TimeSpan.FromMinutes(30)); // PX
await redis.SetAsync("session:42", token, RespireExpiry.At(midnight)); // PXAT
await redis.SetAsync("session:42", token, RespireExpiry.Keep); // KEEPTTL
RespireExpiry is the expiry you send; RespireTtl (returned by Keys.ExpiryAsync) is the expiry Redis reports.
Bulk operations
await redis.Strings.SetManyAsync(
("feature:a", "on"),
("feature:b", "off"));
// One shared expiry (and optional NX/XX) for every pair — Redis MSETEX.
await redis.Strings.SetManyExpireAsync(
TimeSpan.FromMinutes(5),
SetWhen.NotExists,
("feature:a", "on"),
("feature:b", "off"));
string?[] values = await redis.Strings.GetManyAsync("feature:a", "feature:b");
long removed = await redis.DeleteAsync("feature:a", "feature:b");
Variadic APIs use params ReadOnlySpan<T> where possible, avoiding a params-array allocation on supported C# toolchains. Because a params parameter must come last, each of these has a sibling overload taking the items non-params plus a required CancellationToken:
string?[] values = await redis.Strings.GetManyAsync(keys, cancellationToken);
long removed = await redis.DeleteAsync(keys, cancellationToken);
Key lifetime
await redis.ExpireAsync("session:42", TimeSpan.FromMinutes(30));
RespireTtl ttl = await redis.Keys.ExpiryAsync("session:42");
await redis.Keys.ExpireAsync("session:42", RespireExpiry.Persist);
await redis.Keys.ExpireAsync("report", RespireExpiry.At(DateTimeOffset.UtcNow.AddDays(1)));
await redis.Keys.ExpireAsync("lease", TimeSpan.FromMinutes(10), ExpireWhen.GreaterThan);
var value = await redis.Strings.GetAndExpireAsync("session:42", TimeSpan.FromMinutes(30));
CachedJob? job = await redis.Strings.GetAndDeleteAsync<CachedJob>("jobs:next");
The generic combined-get forms deserialize through the client's configured serializer. Hash
field equivalents use the same GetAndDeleteAsync and GetAndExpireAsync naming.
TypeAsync returns RespireKeyType rather than a server string. Conditional rename and copy are
available without dropping to raw commands:
RespireKeyType type = await redis.Keys.TypeAsync("session:42");
bool renamed = await redis.Keys.TryRenameAsync("draft", "published");
bool copied = await redis.Keys.CopyAsync("template", "working-copy", replace: true);
Scan safely
ScanAsync manages Redis cursors and returns an async stream:
await foreach (string key in redis.Keys.ScanAsync(
match: "session:*",
countHint: 500,
type: RespireKeyType.Hash,
cancellationToken: stoppingToken))
{
await InspectAsync(key);
}
countHint maps to Redis COUNT; it guides work per iteration but does not guarantee page size.
Prefer SCAN over KEYS in production; each page yields control and avoids a single server-blocking sweep.
Key-prefixed views
Create a lightweight client view when one service or tenant needs a namespace:
IRespireClient tenant = redis.WithKeyPrefix("tenant:42:");
await tenant.SetAsync("settings", json); // tenant:42:settings