Skip to content

Object Metadata

Tom Laird-McConnell edited this page May 24, 2026 · 3 revisions

Object Metadata and Concurrency

LottaDB attaches storage metadata (key, ETag, JSON) to objects as sidecar data using ConditionalWeakTable. This keeps domain objects clean -- no base classes, no interfaces, no extra properties required.

Extension Methods

Every object returned from LottaDB has metadata attached automatically:

var actor = await db.GetAsync<Actor>("alice");

actor.GetKey();   // "alice"
actor.GetETag();  // "W/\"...\""  -- for optimistic concurrency
actor.GetJson();  // "{\"Username\":\"alice\",...}" -- cached JSON from read

GetKey() / SetKey()

GetKey() returns the storage key. It checks two sources in order:

  1. Key from ObjectMetadata (set by LottaDB on read/save)
  2. Value of the property marked with [Key] (via cached reflection using runtime type)

This means GetKey() works on both objects returned from LottaDB and freshly created objects with a [Key] property:

var fromDb = await db.GetAsync<Actor>("alice");
fromDb.GetKey();  // "alice" (from metadata)

var fresh = new Actor { Username = "bob" };
fresh.GetKey();  // "bob" (from [Key] attribute on Username)

GetETag() / SetETag()

GetETag() returns the ETag for optimistic concurrency. Set automatically by LottaDB on every read (GetAsync, GetManyAsync, Search) and write (SaveAsync). Azure Table Storage uses HTTP-style ETags (W/"..."); the SQLite provider uses integer ETags for atomic concurrency.

GetJson() / SetJson()

GetJson() returns the cached JSON representation from the last read. This is a snapshot -- it is not updated when the object is mutated in memory. Useful for serializing to a web API response without an extra JsonSerializer.Serialize() call.

Web API Pattern

For HTTP APIs with standard ETag-based concurrency:

// GET endpoint
var actor = await db.GetAsync<Actor>(id);
Response.Headers.ETag = actor.GetETag();
return actor;

// PUT endpoint
var updated = JsonSerializer.Deserialize<Actor>(requestBody);
updated.SetETag(Request.Headers.IfMatch);  // attach client's ETag
await db.SaveAsync(updated);               // conditional write

JSON Documents

The same metadata works with JsonDocument for JsonSchema:

var doc = await db.GetAsync(key);
doc.GetKey();   // the document key
doc.GetETag();  // for conditional writes

// Conditional write -- auto-detected from ETag
await db.SaveAsync(doc);

How It Works

Metadata is stored in a single ConditionalWeakTable<object, ObjectMetadata> keyed by object identity. This means:

  • Metadata set via object is readable via T and vice versa
  • Metadata is garbage-collected when the object is collected (no memory leaks)
  • The [Key] property lookup uses obj.GetType() (runtime type) with a ConcurrentDictionary<Type, PropertyInfo?> cache -- reflection happens once per type

Clone this wiki locally