Skip to main content

Hash import sessions

Redis 8.10 adds HIMPORT, which prepares an ordered list of field names once and reuses it to import hashes. Each fieldset belongs to one connection. Use a dedicated RespireHashImportSession so preparation and imports stay on that connection, even when the parent client uses several multiplexed connections.

await using var imports = await redis.Hashes.CreateImportSessionAsync();
await imports.PrepareAsync("person", "name", "city");
await imports.SetAsync("person:1", "person", "Ada", "London");
await imports.SetAsync("person:2", "person", "Grace", "New York");
bool removed = await imports.DiscardAsync("person");
long remainingRemoved = await imports.DiscardAllAsync();

PrepareAsync and SetAsync return true for OK. Preparation replaces an existing fieldset with the same name. Duplicate fields, null arguments, and empty field/value lists are rejected locally. Redis rejects unknown fieldsets and value counts that differ from the prepared field count. Values pair with fields in their original supplied order.

SetAsync replaces the entire hash and clears its expiration. Fields from the old hash disappear. An existing non-hash key causes WRONGTYPE and retains its value and expiration. DiscardAsync returns whether the fieldset existed; DiscardAllAsync returns how many fieldsets it removed. Discarding fieldsets leaves imported hashes intact.

Batches and transactions​

Create queues through the session. Ordinary client queues reject these connection-local APIs. Session queues accept only the four HIMPORT operations.

await using var imports = await redis.Hashes.CreateImportSessionAsync();
using var batch = imports.CreateBatch();
var prepared = batch.Hashes.PrepareImport("person", "name", "city");
var imported = batch.Hashes.Import("person:3", "person", "Katherine", "Hampton");
var discarded = batch.Hashes.DiscardImport("person");
var cleared = batch.Hashes.DiscardAllImports();
await batch.ExecuteAsync();
Console.WriteLine(imported.Result);
await using var imports = await redis.Hashes.CreateImportSessionAsync();
await using var transaction = imports.CreateTransaction();
var prepared = transaction.Hashes.PrepareImport("person", "name", "city");
var imported = transaction.Hashes.Import("person:4", "person", "Dorothy", "Hampton");
var discarded = transaction.Hashes.DiscardImport("person");
await transaction.CommitAsync();
Console.WriteLine(imported.Result);

Batches pipeline commands in order. Transactions use MULTI/EXEC on the same connection; the session confirms MULTI before sending any imports. A rejected MULTI leaves prepared fieldsets usable. Queue-time errors, such as a denied HIMPORT subcommand, preserve the session and its fieldsets when the drained EXEC confirms that Redis discarded the transaction. The original queue error still faults the commit and every queued pending. A rejected EXEC closes the session because Redis may still be in transaction mode. Transactions require one in-flight slot per queued command plus one for EXEC; exceeding MaxInflightCommands fails before MULTI and preserves prepared fieldsets. Credential renewal waits outside the complete MULTI/EXEC sequence. Credential expiry and renewal deadlines still apply; a transaction cannot extend the connection's authentication lifetime. Execution errors fault the affected pending and do not roll back other commands. Session transactions do not provide WATCH. ExecuteAndWaitForReplicationAsync and ExecuteAndWaitForAofAsync reject session batches because their execution contract creates a fresh connection. Await each operation or queue execution before starting another operation.

Ownership, routing, and failures​

Names, fields, keys, and values support binary arguments. The session snapshots their bytes before an asynchronous send or enqueue, so callers can reuse their buffers after the call. Key prefixes apply only to imported keys; fieldset names and fields remain unchanged. Imports invalidate cached results for the affected key. Batch and transaction execution also uses the existing conservative cache fences.

For Redis Cluster, supply a routing key when opening the session:

await using var imports = await redis.Hashes.CreateImportSessionAsync("{people}:anchor");
await imports.PrepareAsync("person", "name");
await imports.SetAsync("{people}:1", "person", "Ada");

The routing key is not read or written. All imported keys must share its effective slot after prefixing. A session cannot span Cluster slots. A route change, MOVED/ASK redirect, connection loss, timeout, or cancellation after sending invalidates the session. Commands are never replayed onto another connection. Create a new session and prepare its fieldsets again.

Always use await using. Disposal closes the dedicated connection rather than returning its fieldsets to another caller. Connection closure or Redis RESET drops all fieldsets; they have no independent expiry timer. Disposing the parent client also invalidates the session. Disposal does not wait for an in-flight operation: it closes the connection, causing that operation to fail without replay.

Ordinary command errors leave the session usable. Routing errors, READONLY, and errors that leave transaction state uncertain invalidate it. Cancellation proven to occur before admission leaves its fieldsets intact, including a batch waiting for credential renewal. Cancellation of an admitted command still expires the session. After an uncertain send, some imports may have executed; inspect application data before retrying. Disposing a queue before execution does not dispose the session or discard fieldsets prepared earlier.

RespireFakeServer supports all four operations with the same per-connection isolation, overwrite behavior, validation, and WATCH invalidation. Other server versions must support HIMPORT themselves; opening a session does not negotiate this feature.