Skip to main content

Twin Reader

Adapters can read the current persisted twin via AdapterContext.Twin (the ITwinReader SDK service). Use it whenever you need the desired/reported/tags state without hand-writing repository queries — most commonly inside a reconciling poller or webhook handler.

Contract

public interface ITwinReader
{
Task<TwinSnapshot?> ReadAsync(string deviceId, CancellationToken ct = default);
}

public sealed record TwinSnapshot
{
public required string DeviceId { get; init; }
public JsonObject? Desired { get; init; }
public int DesiredVersion { get; init; }
public JsonObject? Reported { get; init; }
public int ReportedVersion { get; init; }
public JsonObject? Tags { get; init; }
public DateTime LastActivity { get; init; }
}

BaseAdapter exposes the seam as protected ITwinReader Twin parallel to Telemetry, Properties, and Diagnostics. Background services and controllers can inject ITwinReader directly via DI.

Behaviour

  • Returns null for a device that does not exist or is soft-deleted.
  • Hits the repository on every call — no SDK-level cache. Adapters that poll the same deviceId at high frequency should cache locally.
  • DesiredVersion and ReportedVersion are 0 for newly-created twins that have never been written; they increment on every successful PATCH.
  • LastActivity is UTC.

Common pattern: diff-against-truth-of-record

The ReconcilingPoller<T> base class uses Twin.ReadAsync internally so adapter code stays focused on upstream IO + projection. If you write a custom poller, the same shape applies:

foreach (var item in upstreamItems)
{
var twin = await Twin.ReadAsync(deviceId, ct);
var newReported = ProjectToReported(item);
if (!Equals(twin?.Reported, newReported))
{
Properties.Report(deviceId, newReported);
}
}

This avoids the in-process cache that would otherwise re-emit on every process restart.

When NOT to use it

  • For high-throughput telemetry forwarding, write directly via Telemetry.Send — the pipeline owns its own deduplication.
  • For per-property granular reads, prefer the twin-management REST endpoints with JSON Pointer projection.

See also