Skip to content

Data and Stores

František Bereň edited this page Sep 18, 2026 · 2 revisions

Data and Stores

One store API, thirteen backends. Your model and your calls stay the same; only the store class and its settings change.

Choosing a backend

Backend Project Good for
InMemory Birko.Data.InMemory tests, prototypes. No settings, no persistence. Start here.
SQLite Birko.Data.SQL.SqLite desktop apps, small services, local files
PostgreSQL Birko.Data.SQL.PostgreSQL the default choice for a server application
MySQL Birko.Data.SQL.MySQL when the database is already MySQL
SQL Server Birko.Data.SQL.MSSql Microsoft estates
TimescaleDB Birko.Data.TimescaleDB time series on PostgreSQL (hypertables)
MongoDB Birko.Data.MongoDB documents, flexible shape
ElasticSearch Birko.Data.ElasticSearch search, large read-heavy sets
RavenDB Birko.Data.RavenDB documents with built-in indexing
CosmosDB Birko.Data.CosmosDB Azure-hosted documents
InfluxDB Birko.Data.InfluxDB metrics and time series
JSON Birko.Data.JSON config-like data, human-readable files
XML Birko.Data.XML legacy interop

💡 Not sure? InMemory to learn, SQLite for a desktop or single-node app, PostgreSQL for a server. You can change later — that is the point of the abstraction.

The same code, three backends

// tests
var store = new InMemoryStore<Product>();

// local file
var store = new SQLiteStore<Product>();
store.SetSettings(new SqLiteSettings(@"C:\data", "shop.db"));

// document database
var store = new MongoDBStore<Product>();
store.SetSettings(new MongoDBSettings { /* host, port, database */ });

Everything after that line is identical.

Filters

Filters are ordinary C# expressions, translated per backend:

store.Read(x => x.Price < 10m, null, null, null);
store.Read(x => x.Name.Contains("widget"), null, null, null);
store.Read(x => x.Price > 5m && x.Name.StartsWith("A"), null, null, null);

var ids = new List<Guid?> { id1, id2 };         // List, not array - and Guid? - see below
store.Read(x => ids.Contains(x.Guid), null, null, null);

⚠️ Use a List<T>, not an array, for set membership. On .NET 9+ array.Contains(x) binds to MemoryExtensions.Contains(ReadOnlySpan<T>, T), and a ref struct cannot appear in an expression tree, so the filter will not compile. A List<T> binds to Enumerable.Contains and works. (AbstractModel.Guid is Guid?, so the list element type is Guid? too.)

⚠️ Not every expression translates on every backend. A filter that a backend cannot express raises NotSupportedException rather than silently returning wrong rows — that is deliberate, and several past bugs came from backends that used to fail quietly instead.

⚠️ Prefer a captured collection to a method call inside a filter. Guards may evaluate the expression more than once, so a side-effecting operand can run repeatedly.

Bulk operations

store.Create(new[] { a, b, c });

store.Delete(x => x.Discontinued);                     // delete by filter
store.Update(x => x.Discontinued, p => p.Price = 0m);  // update by filter

store.DeleteAll();                                      // explicit "everything"

Why DeleteAll() and not Delete()? A destructive all-rows operation gets a long, obvious name. Delete() would sit one keystroke from Delete(items). Similarly, a filter that would match every row throws WholeTableWriteException — say DeleteAll() if you mean it.

Decorators — behaviour without touching the store

Wrap a store to add cross-cutting behaviour:

Decorator Adds
Timestamp sets CreatedAt / UpdatedAt
SoftDelete DeletedAt instead of removing rows
Audit who changed what
Versioned optimistic concurrency
Cached read caching
Tenant multi-tenant row scoping
Localization per-culture text fields

Built with StoreWrapperBuilder (Birko.Data.Composition). Order matters — a decorator that suppresses a write also suppresses everything inside it.

Related projects

  • Birko.Data.Migrations — versioned schema changes, per backend. See SQL Databases.
  • Birko.Data.Sync — synchronise data between two stores, including bidirectional.
  • Birko.Data.Views — read-only projections and aggregates.
  • Birko.Data.Tenant — multi-tenancy, with an ambient tenant context.
  • Birko.Data.Tagging — tag any entity.
  • Birko.Data.EventSourcing — an event-sourced store.

Next

  • SQL Databases — attributes, indexes, providers, transactions
  • Core Concepts — if stores, settings and models still feel unclear

Clone this wiki locally