Basic operations
This article covers the single-key operations you use most: store a value, read it back, delete it, and check how many keys a store holds. It also shows how to read the KvResult that each operation returns so you can tell success from a missing key or a rejected write.
Every method here is asynchronous and returns a result object. Zaris does not throw an exception for an expected outcome such as a missing key; you inspect the result instead.
The KvStatus enum referenced below lives in the Clustron.Zaris.Abstractions namespace. Add using Clustron.Zaris.Abstractions; next to the using Clustron.Zaris.Client; from your client bootstrap.
Store a value
Call PutAsync<T> to store a value under a key. The method returns a KvResult describing the outcome.
var result = await client.PutAsync("hello", "world");
if (!result.IsSuccess)
{
Console.WriteLine($"Put failed: {result.Status} {result.Error}");
}
By default, a put overwrites any existing value for the key. When it overwrites, result.IsUpdate is true; when it creates a new key, IsUpdate is false. To store only when the key does not already exist, pass Put.IfAbsent() (see Options).
The signature is:
ValueTask<KvResult> PutAsync<T>(
string key,
T value,
PutOptions? options = null,
CancellationToken ct = default);
The value can be any type the client can serialize, not just a string. See Serialization for how objects are encoded and for the special handling of byte[].
ValueTaskAs of 1.1.0, the single-key operations — PutAsync, GetAsync, DeleteAsync, ExpireAsync, PersistAsync, and GetTimeToLiveAsync — return ValueTask/ValueTask<T> instead of Task, so a call that completes synchronously allocates nothing. Awaiting them with await is exactly the same as before. Only if you treat the result as a Task — Task.WhenAll(...), assigning to a Task<T> variable, or .ContinueWith(...) — call .AsTask() on it first, and don't await the same returned value twice. The bulk (PutManyAsync/GetManyAsync/DeleteManyAsync), CountAsync, ClearAsync, transaction, and counter APIs still return Task. See Versioning and compatibility.
Retrieve a value
Call GetAsync<T> and specify the type you expect. The result is a KvResult<T>; read the value from result.Value only after you confirm the read succeeded.
var result = await client.GetAsync<string>("hello");
if (result.IsSuccess)
{
Console.WriteLine(result.Value);
}
else if (result.Status == KvStatus.NotFound)
{
Console.WriteLine("Key not found");
}
When the key is missing, IsSuccess is false and Status is KvStatus.NotFound. Distinguishing NotFound from other statuses matters: a NotFound means the key is absent, while a status such as Timeout or Unavailable means the read itself did not complete and may be worth retrying.
The type parameter T must match the type you stored under the key. Reading a value as the wrong type produces a deserialization error, not a silent conversion. Keep the stored type and the requested type consistent.
Delete a value
Call DeleteAsync to remove a key and its value.
var result = await client.DeleteAsync("hello");
Deleting a key that does not exist is safe: the call returns either success or KvStatus.NotFound rather than throwing. Check result.Mutated if you need to know whether the delete actually removed something.
Interpret the result
Both KvResult and its generic form KvResult<T> carry the outcome of an operation. Use these members rather than exception handling for expected cases.
| Member | Type | Description |
|---|---|---|
IsSuccess | bool | true when Status is Success and there is no error. Check this first. |
Status | KvStatus | The specific outcome: Success, NotFound, Conflict, Timeout, Unavailable, and others. |
Error | string? | A human-readable message when the operation failed. |
Value | T? | The retrieved value. Present only on KvResult<T> from a read. |
IsUpdate | bool | On a put, true when an existing value was overwritten. |
Mutated | bool | true when the operation actually changed the store. |
Version | ItemVersion? | The item's version, used for optimistic concurrency (see Options). |
Status is a KvStatus enum. Common values you handle are Success, NotFound (key absent), Conflict (a conditional write was rejected), and the transient Timeout and Unavailable.
Manage time-to-live
You can change a key's expiration without rewriting its value. These operations act on a key that already exists.
// Set or replace the TTL of an existing key.
await client.ExpireAsync("session:42", TimeSpan.FromMinutes(30));
// Read the remaining TTL. Value is null when the key has no TTL.
var ttl = await client.GetTimeToLiveAsync("session:42");
// Remove the TTL so the key never expires.
await client.PersistAsync("session:42");
GetTimeToLiveAsync returns a KvResult<TimeSpan?>: a positive TimeSpan when a TTL is set, a null value when the key exists but has no TTL, and Status of KvStatus.NotFound when the key is absent. To set a TTL at write time instead, use Put.WithTtl in Options.
ExpireAsync and PersistAsync route through the partition map and are only served by a fully initialized cluster node. In in-process mode they return KvStatus.Unavailable ("partition map not initialized"). If you are running in-process, set a key's TTL at write time with Put.WithTtl instead; GetTimeToLiveAsync reads the remaining TTL in both modes.
Count the keys
To find how many entries a store holds, call CountAsync.
var count = await client.CountAsync();
The client also exposes a synchronous Count property, but it performs the same cluster-wide work while blocking the calling thread on network I/O. Prefer CountAsync in asynchronous code.
A complete flow
The following example writes a value, reads it back, and deletes it.
await client.PutAsync("user:1", "Ali");
var result = await client.GetAsync<string>("user:1");
if (result.IsSuccess)
{
Console.WriteLine(result.Value);
}
await client.DeleteAsync("user:1");