Skip to content

Core Concepts

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

Core Concepts

Four ideas carry most of Birko. Learn these and the rest of the framework reads naturally.

1. Model — your data

A plain C# class. Inherit from one of three bases depending on what you need:

Base Gives you Use for
AbstractModel Guid id anything stored
AbstractDatabaseModel + SQL attributes support SQL tables
AbstractLogModel + CreatedAt, UpdatedAt rows you want timestamped
public class Product : AbstractModel
{
    public string Name { get; set; } = string.Empty;
    public decimal Price { get; set; }
}

No base class is required by the store interfaces themselves — but the bases give you the identity and timestamps that the decorators and repositories expect.

2. Settings — where the data lives

Settings say where a thing is and how to reach it. They form a chain, so each provider only adds what it actually needs:

Settings                    Location, Name
 └─ PasswordSettings        + Password
     └─ RemoteSettings      + UserName, Port, UseSecure
         └─ SqlSettings     + CommandTimeout, ConnectionTimeout
             └─ MSSqlSettings / MySqlSettings / PostgreSqlSettings / TimescaleDBSettings
var settings = new SqLiteSettings(@"C:\data", "shop.db");     // file-based: location + name

💡 A provider's settings class ships with the provider, not with Birko.Configuration — SqLiteSettings is in Birko.Data.SQL.SqLite.Stores. Look in the provider's namespace.

⚠️ Two settings objects with the same Location:Name are treated as the same database. Connectors are cached process-wide on that key, so a second settings object differing only in CommandTimeout silently reuses the first one's connector.

3. Store — the CRUD API

A store is the thing you actually call. The same eight or so methods exist for every backend:

var id      = store.Create(product);              // returns the Guid
var one     = store.Read(id);
var matches = store.Read(x => x.Price < 10m, null, null, null);
              store.Update(product);
              store.Delete(product);
var n       = store.Count();

Sync and async are separate types

Not Store with async methods — different classes:

var store      = new SQLiteStore<Product>();        // sync
var asyncStore = new AsyncSQLiteStore<Product>();   // async: CreateAsync, ReadAsync, …

Bulk operations

Collections and filter-based writes:

store.Create(new[] { a, b, c });        // create many
store.Delete(x => x.Price > 100m);      // delete by filter
store.Update(x => x.Discontinued, p => p.Price = 0m);   // update by filter

⚠️ On a bulk store, Read(filter) returns the collection, not one item. The bulk overload hides the single-result one. For a single match use ReadFirst(filter).

⚠️ A destructive operation that would hit every row is refused. Delete(x => true) is the explicit "yes, everything" door; a filter that merely reduces to everything throws WholeTableWriteException. This has caught real bugs — it is not paranoia.

Sorting and paging

using Birko.Data.Stores;

var page = store.Read(
    filter:  x => x.Price > 0m,
    orderBy: OrderBy<Product>.By(p => p.Name),   // .ByDescending(…) too
    limit:   20,
    offset:  40);

Lazy initialisation

You never call Init(). The first CRUD call creates the table or file if needed. That is why the Getting Started example works with no setup step.

4. Repository — optional layer above the store

A store persists a model. A repository adds domain behaviour and can map between a view model (what your API exposes) and a model (what you store). If you don't need that, use stores directly — plenty of applications do.

IStore        Create / Read / Update / Delete / Count
IBulkStore    + collections and filter-based writes
IRepository   the same, plus view-model mapping and domain rules

Putting it together

// 1. model
[Table("Products")]
public class Product : AbstractDatabaseModel
{
    [MaxLengthField(200)] public string Name { get; set; } = string.Empty;
    [PrecisionField(18), ScaleField(2)] public decimal Price { get; set; }
}

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

// 4. use it — the table is created on first call
store.Create(new Product { Name = "Widget", Price = 9.99m });

Swap SQLiteStore + SqLiteSettings for MongoDBStore + its settings and nothing else changes.

Next

Clone this wiki locally