Type Hierarchies
MemDb supports T as an interface or a base class — in both cases, multiple concrete types can be stored in the same dataset and retrieved through the shared type.
Default Serializer
With the default System.Text.Json serializer, decorate the interface or base class with [JsonDerivedType] attributes — one per concrete type. The type discriminator (an int or string) is written alongside each record and used on read to resolve the concrete type:
// T as interface — attributes go on the interface
[JsonDerivedType(typeof(TextAsset), typeDiscriminator: (int)DigitalAssetType.Text)]
[JsonDerivedType(typeof(JsonAsset), typeDiscriminator: (int)DigitalAssetType.Json)]
[JsonDerivedType(typeof(ExtensionlessAsset), typeDiscriminator: (int)DigitalAssetType.Unknown)]
public interface IDigitalAsset { ... }
// T as base class — attributes go on the base class
[JsonDerivedType(typeof(TextAsset), typeDiscriminator: (int)DigitalAssetType.Text)]
[JsonDerivedType(typeof(JsonAsset), typeDiscriminator: (int)DigitalAssetType.Json)]
[JsonDerivedType(typeof(ExtensionlessAsset), typeDiscriminator: (int)DigitalAssetType.Unknown)]
public abstract class DigitalAsset { ... }Configuration and usage are the same in both cases — only the type argument changes:
MemDb.ConfigureFor<IDigitalAsset>("IAssets", _dbDirectory).Register(); // interface
MemDb.ConfigureFor<DigitalAsset>("AbstractAssets", _dbDirectory).Register(); // base class
// --- interface example ---
using (var db = MemDb.Open<IDigitalAsset>("IAssets"))
{
db.Insert(new TextAsset { Name = "readme.txt", Directory = "/docs" });
db.Insert(new JsonAsset { Name = "config.json", Directory = "/config" });
db.Insert(new ExtensionlessAsset { Name = "Makefile", Directory = "/build" });
IDigitalAsset[] all = db.FindAll(a => true);
IDigitalAsset[] textFiles = db.FindAll(a => a.AssetType == DigitalAssetType.Text);
}
// --- base class example ---
using (var db = MemDb.Open<DigitalAsset>("AbstractAssets"))
{
db.Insert(new TextAsset { Name = "readme.txt", Directory = "/docs" });
db.Insert(new JsonAsset { Name = "config.json", Directory = "/config" });
db.Insert(new ExtensionlessAsset { Name = "Makefile", Directory = "/build" });
DigitalAsset[] all = db.FindAll(a => true);
DigitalAsset[] textFiles = db.FindAll(a => a.AssetType == DigitalAssetType.Text);
}Configuring derived types without attributes
To configure [JsonDerivedType] without attributes, use TypeInfo modifiers (.NET 7+) to inject polymorphic metadata into the System.Text.Json contract at runtime — useful when the types you need to store live in code bases you do not own.
Forward compatibility
Because the type discriminator is written into each record on disk, the set of [JsonDerivedType] attributes registered on the type forms part of the dataset’s contract. Adding a new derived type to an existing dataset is safe — records previously written with the old discriminators still deserialize correctly.
Reading a record whose discriminator is not registered on the type results in a JsonException from System.Text.Json. If a derived type is removed or renamed in code, dataset records written under the old discriminator will fail to read until either the type is restored or a custom serializer takes over.
Custom Serializer
With a custom serializer, no attributes are needed. The serializer writes a type discriminator on write and uses it to instantiate the correct concrete type on read — the same role [JsonDerivedType] plays for the default serializer. This also gives full control over the failure mode for unknown discriminators (skip, fall back to a sentinel type, throw a domain-specific exception, etc.).