Basic Operations

Basic Operations

This page covers the read/write API on MemDb<T>. For multi-record filtering, sorting, aggregation, and query-scoped writes, see Queries.

AppendOnly mode restrictions. In AppendOnly mode only Insert and the unfiltered Count() are permitted. Predicate-based and id-based reads (Find, FindAll, Exists, Count(predicate)), Update, and Delete all throw InvalidOperationException. See Modes.

Operations at a glance

OperationBy idBy predicateReturns
Insertn/a — id is auto-assignedn/avoid (id captured via callback)
Updatebool (by id) / int (by predicate)
Deletebool (by id) / int (by predicate)
Existsbool
Count✓ (or unfiltered)int
FindT or null
FindAll✓ (variadic ids)T[]

When the identity index is enabled, by-id operations use an O(1) hash lookup; when it is disabled, they fall back to a full scan.

Insert

Records receive an auto-assigned identity key — a long, incremented from 1, assigned thread-safely. The id is returned via an action callback the caller can capture or ignore. There is no required property name or interface; naming the property Id is convention only.

// Capture the assigned id via callback
Person p1 = new Person { FirstName = "Eric", LastName = "Cartman", BirthDate = DateTime.Now.AddYears(-10) };
db.Insert(p1, (id) => p1.Id = id);

// Capture the assigned id and encrypt at rest
Person p2 = new Person { FirstName = "Stan", LastName = "Marsh", BirthDate = DateTime.Now.AddYears(-10) };
db.Insert(p2, (id) => p2.Id = id, encrypt: true);

// Insert without capturing the id
Person p3 = new Person { FirstName = "Kyle", LastName = "Broflovski", BirthDate = DateTime.Now.AddYears(-10) };
db.Insert(p3);

// Insert encrypted without capturing the id
Person p4 = new Person { FirstName = "Kenny", LastName = "McCormick", BirthDate = DateTime.Now.AddYears(-10) };
db.Insert(p4, encrypt: true);

Update

Update records by id or by predicate:

// By id (O(1) hash lookup when identity index is configured) — returns bool (true if the record was found and updated)
bool updated = db.Update(apply: p => p.MiddleName = "Theodore", id: p.Id);

// By predicate — returns int (count of records updated)
int count = db.Update(
    apply: p => p.BirthDate = p.BirthDate.AddYears(1),
    where: p => p.LastName is not null
);

Update does not mutate records in place — the record is deep-copied, the original marked stale, and the updated clone appended. See Architecture — Record lifecycle.

A record’s encryption state is set at insert and preserved through every update — Update has no encrypt parameter. To change a record’s encryption state, delete and re-insert it.

Delete

Delete records by id or by predicate. Return type — bool for by-id, int for by-predicate.

// By id (O(1) hash lookup when identity index is configured) — returns bool (true if the record was found and deleted)
bool deleted = db.Delete(id);

// By predicate — returns int (count of records found and deleted)
int count = db.Delete(p => p.Ssn is null);

Exists

Check whether a record exists without retrieving it.

// By id (O(1) hash lookup when identity index is configured)
bool exists = db.Exists(id);

// By predicate
bool exists = db.Exists(p => p.Email == "user@example.com");

Count

Returns the total number of records, or the count matching a predicate.

int total   = db.Count();
int matched = db.Count(r => r.IsActive);

Unfiltered Count() is permitted in AppendOnly mode — it reads directly from the always-initialized record map. Count(predicate) requires ReadWrite or ReadOnly.

Find

Returns the first matching record, or null if no match is found.

// By id (O(1) hash lookup when identity index is configured)
var record = db.Find(42);

// By predicate
var record = db.Find(r => r.Email == "user@example.com");

FindAll

Returns a T[] of matching records, or Array.Empty<T>() if no matches are found.

// By id set (O(1) hash lookups when identity index is configured)
var records = db.FindAll(1, 2, 3);

// By predicate
var records = db.FindAll(r => r.Department == "Engineering");