Skip to main content

Options

Options let you control how a write behaves without changing how you call PutAsync. This article shows you how to build PutOptions with the fluent Put helper to set a TTL, tag data for search, attach a lease or lock, and apply conditional writes.

A plain put stores a value and overwrites any existing one. Options add the extra behavior real applications need: expiration, searchable metadata, ownership, and concurrency control.

Build options with the Put helper

Create PutOptions with the static Put class. Each method starts or extends a fluent chain, and you pass the result as the third argument to PutAsync.

var options = Put.WithEntity("customer")
.WithLabel("city", "London")
.WithTtl(TimeSpan.FromMinutes(5));

await client.PutAsync("cust:1", customer, options);

The methods that begin a chain (Put.WithEntity, Put.WithTtl, Put.IfAbsent, and so on) each return a PutOptions; the same methods then extend it, so you can compose them in any order.

Group data with an entity

An entity is a logical name for a group of keys, such as customer or order. Set it with WithEntity. The entity is what makes an item queryable, so define one for any data you plan to search.

Put.WithEntity("customer")

Add searchable labels

Labels are named fields attached to an item, such as city = London. Add them with WithLabel. Labels are what a search query filters on, so tag your items with the fields you want to query by.

Put.WithEntity("customer")
.WithLabel("city", "London")
.WithLabel("age", "30")

Indexed versus non-indexed labels

By default a label is indexed, which means it is searchable. Pass indexed: false to attach a label as plain metadata that is stored with the item but not searchable.

.WithLabel("debug", "trace-id-123", indexed: false)

Use non-indexed labels for descriptive data you want to read back but never query on. Indexing every label makes writes and the index larger, so index only the fields you actually search.

Set a time-to-live

Attach a TTL with WithTtl to have the key removed automatically after a duration. Use this for data that should expire, such as sessions or caches.

Put.WithTtl(TimeSpan.FromMinutes(10))

A TTL controls expiration only. To tie a key's lifetime to ownership instead, attach a lease.

Write conditionally

Conditional options let a write succeed only when a precondition holds, which prevents you from clobbering data another writer changed.

Only if absent

IfAbsent writes the value only when the key does not already exist. Use it to claim a key without overwriting an existing owner.

Put.WithEntity("order").IfAbsent()

When the key already exists, the put does not overwrite it and the result reports a KvStatus.Conflict.

Only if the version matches

Put.WithIfMatch writes only when the key's current version matches the one you pass. This is optimistic concurrency: you read a value, keep its version, and write back only if nobody else changed the key in between.

var current = await client.GetAsync<Order>("order:1");

var options = Put.WithIfMatch(current.Version);
await client.PutAsync("order:1", updated, options);

The version comes from a previous result's Version property (an ItemVersion?). If the stored version has moved on, the write is rejected with a conflict rather than overwriting the newer data.

Attach a lease or lock

You can bind a write to a coordination primitive so the store enforces ownership or exclusivity.

  • WithLease associates the key with a lease. When the lease expires, the key is removed. Pass the lease's Id.
  • WithLock performs the write under a lock you hold, so the operation is safe against concurrent writers.
await client.PutAsync("worker:1", "active",
Put.WithEntity("worker").WithLease(lease.Id));
await client.PutAsync("resource:1", state,
Put.WithLock(lockHandle));

See Leases and Locks for how to acquire these handles.

Describe the format with a content type

WithContentType tags the stored value with a format string, such as application/json. This is descriptive metadata for interoperability; you read it back from an item's metadata.

Put.WithContentType("application/json")
note

The content type does not change how the value is encoded. Zaris serializes values with its own serializer regardless of this tag, so setting application/json does not make the stored bytes JSON. See Serialization for how values are actually encoded.

Combine options

All options compose into one chain, so you can express several requirements in a single put.

var options = Put.WithEntity("order")
.WithLabel("status", "pending")
.WithTtl(TimeSpan.FromMinutes(5))
.IfAbsent();

await client.PutAsync("order:1", order, options);

Choosing an option

Match the requirement to the option:

RequirementOption
Make data queryableWithEntity plus WithLabel
Expire data after a durationWithTtl
Tie a key's lifetime to ownershipWithLease
Reject overwrites of newer dataWithIfMatch
Create a key only if it is absentIfAbsent
Write under a held lockWithLock

Next steps