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 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
| Operation | By id | By predicate | Returns |
|---|---|---|---|
Insert | n/a — id is auto-assigned | n/a | void (id captured via callback) |
Update | ✓ | ✓ | bool (by id) / int (by predicate) |
Delete | ✓ | ✓ | bool (by id) / int (by predicate) |
Exists | ✓ | ✓ | bool |
Count | — | ✓ (or unfiltered) | int |
Find | ✓ | ✓ | T 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");