Skip to content
Document DB v12 - Improved Interceptors with Soft Delete Integration, AI protections, & Admin UI with Aspire Integration! How!?

Stores

The store is the vector database: it holds one row per enrolled shot and answers “which stored vectors are nearest this one”.

public interface IFaceStore
{
Task Add(Person person, CancellationToken ct = default);
Task<IReadOnlyList<FaceMatch>> FindNearest(ReadOnlyMemory<float> embedding, int count, CancellationToken ct = default);
Task<IReadOnlyList<Person>> GetAll(CancellationToken ct = default);
Task<int> RemoveByPersonIdentifier(string personIdentifier, CancellationToken ct = default);
}
Terminal window
dotnet add package Shiny.FaceIntelligence.DocumentDb.Sqlite
face.UseSqliteStore(o =>
{
o.ConnectionString = $"Data Source={Path.Combine(FileSystem.AppDataDirectory, "faces.db")}";
});

This is a convenience wrapper over UseDocumentDbStore that wires a vector-enabled SQLite provider through Shiny.DocumentDb.Sqlite, backed by sqlite-vec (vec0) for approximate nearest-neighbour search.

Server-side, you probably want Postgres/pgvector, SQL Server, Cosmos or Mongo rather than SQLite. Use the provider-agnostic package and hand it a provider factory:

Terminal window
dotnet add package Shiny.FaceIntelligence.DocumentDb
face.UseDocumentDbStore(sp => new PostgreSqlDatabaseProvider(connectionString));

The vector dimension is read from the registered IFaceEmbedder, so it always matches the model — you never declare it, and it cannot drift. See DocumentDb — Vector / ANN Search for what each backend supports.

Implement IFaceStore and register it directly:

face.UseStore(sp => new MyFaceStore(sp.GetRequiredService<IMyBackend>()));

Two contract details worth knowing:

  • FindNearest returns cosine distance, 0 = identical. If your backend reports similarity, convert — the manager compares directly against MaxDistance and a flipped convention will match everything or nothing.
  • Person.Embedding is [JsonIgnore]d. In the DocumentDb stores it lives only in the vector sidecar table and comes back empty from GetAll(). Your implementation may do otherwise, but callers are told not to rely on reading it back.

new DocumentStore(...) is built eagerly when IFaceStore resolves, and that is cheap on purpose: the constructor only creates the connection object and the mapping metadata, all in memory. The store opens the connection and loads the native vector extension itself, lazily, on the first operation. So the database open and the vec0 load land inside the first enroll or recognize call — where your page is already catching errors — not at app startup.