Extensibility
Custom Serialization
By default, MemDb uses System.Text.Json.JsonSerializer. Providing a custom serializer avoids JSON overhead and can significantly improve throughput in performance-critical scenarios.
Implement IMemDbSerializer<T>, which handles both stream-based (BinaryReader/BinaryWriter) and span-based paths:
public class PersonSerializer : IMemDbSerializer<Person>
{
public void Serialize(Person record, BinaryWriter to)
{
to.Write(record.Id);
to.Write(record.FirstName);
to.Write(record.MiddleName is null);
if (record.MiddleName is not null)
to.Write(record.MiddleName);
to.Write(record.LastName);
to.Write(record.BirthDate.ToBinary());
to.Write(record.Ssn);
to.Write(record.CreditScore);
to.Write(record.NetWorth);
}
public byte[] Serialize(Person record)
{
using var ms = new MemoryStream();
using var writer = new BinaryWriter(ms, Encoding.UTF8, leaveOpen: true);
Serialize(record, writer);
return ms.ToArray();
}
public Person Deserialize(BinaryReader from, int length)
{
var record = new Person();
record.Id = from.ReadInt64();
record.FirstName = from.ReadString();
if (!from.ReadBoolean()) record.MiddleName = from.ReadString();
record.LastName = from.ReadString();
record.BirthDate = DateTime.FromBinary(from.ReadInt64());
record.Ssn = from.ReadString();
record.CreditScore = from.ReadInt32();
record.NetWorth = from.ReadDecimal();
return record;
}
public Person Deserialize(ReadOnlySpan<byte> from)
{
using var ms = new MemoryStream(from.ToArray());
using var reader = new BinaryReader(ms);
return Deserialize(reader, from.Length);
}
}Register the serializer during configuration:
MemDb.ConfigureFor<Person>("people", _dbDirectory)
.SerializeWith(() => new PersonSerializer())
.Register();Adding JsonConverters to the default serializer
For niche types that System.Text.Json can’t handle out of the box — custom date formats, value-object wrappers, polymorphism without [JsonDerivedType] — you can attach a JsonConverter<U> to MemDb’s default JSON serializer without writing a full IMemDbSerializer<T>. Resolve the per-type singleton via MemDbJsonSerializer<T>.GetInstance() and call ApplyConverterFor<U>:
MemDbJsonSerializer<Person>
.GetInstance()
.ApplyConverterFor<TaxId>(new TaxIdJsonConverter());
MemDb.ConfigureFor<Person>("people", _dbDirectory)
.Register();ApplyConverterFor clones the serializer’s options, removes any converter targeting the same type, adds the new one, and hot-swaps the options instance. Call it once at startup, before the first MemDb.Open — it is not thread-safe.
Custom Cloning
By default, MemDb deep-copies records via a round-trip through whichever serializer is registered (custom or the default JSON serializer). Providing an IMemDbCloner<T> implementation bypasses serialization entirely and performs a direct field copy — significantly faster for large datasets.
The cloner is also the right place to normalize or sanitize values at the cache boundary — for example, coalescing null to a sentinel value on properties used in applied indexes (which cannot hold null keys).
public class PersonCloner : IMemDbCloner<Person>
{
public Person DeepCopy(Person value)
{
var record = new Person();
record.Id = value.Id;
record.FirstName = value.FirstName;
record.MiddleName = value.MiddleName;
record.LastName = value.LastName;
record.BirthDate = value.BirthDate;
record.Ssn = value.Ssn ?? string.Empty; // null coalesced — Ssn is an applied index
record.CreditScore = value.CreditScore;
record.NetWorth = value.NetWorth;
return record;
}
public Person[] DeepCopy(IList<Person> values)
{
int count = values?.Count ?? 0;
if (count == 0)
return Array.Empty<Person>();
var result = new Person[count];
for (int i = 0; i < count; i++)
result[i] = DeepCopy(values[i]);
return result;
}
}Register the cloner during configuration:
MemDb.ConfigureFor<Person>("people", _dbDirectory)
.SerializeWith(() => new PersonSerializer())
.CloneWith(() => new PersonCloner())
.Register();Unlike serialization, cloning operates entirely in RAM and can be added or changed at any time without affecting persisted data.
See Core Concepts — Data Integrity for the full cloning priority chain.