-
Notifications
You must be signed in to change notification settings - Fork 0
Object Metadata
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.
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 readGetKey() returns the storage key. It checks two sources in order:
- Key from ObjectMetadata (set by LottaDB on read/save)
- 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() 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() 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.
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 writeThe 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);Metadata is stored in a single ConditionalWeakTable<object, ObjectMetadata> keyed by object identity. This means:
- Metadata set via
objectis readable viaTand vice versa - Metadata is garbage-collected when the object is collected (no memory leaks)
- The
[Key]property lookup usesobj.GetType()(runtime type) with aConcurrentDictionary<Type, PropertyInfo?>cache -- reflection happens once per type