ADR 0001 — Data access strategy¶
- Status: Accepted
- Date: 2026-04-27
- Decision drivers: performance under realistic loads, ergonomics, ecosystem fit, ability to swap later
Context¶
Molib will store and query infrastructure, scheduling, and personnel data in PostgreSQL. The .NET ecosystem offers several data-access approaches with very different tradeoffs:
- EF Core — full ORM, strong ergonomics, change tracking, migrations, but historically heavy on hot paths (per-entity materialization, expression-tree translation, default tracking overhead).
- Dapper — micro-ORM. You write SQL; it maps rows to POCOs via IL emit. Near-raw ADO.NET performance, minimal API surface.
- Linq2Db — LINQ-style queries without EF's weight (no change tracking, no identity map). Conceptually similar to Ruby's Arel: a faithful, composable bridge from a host-language expression to SQL. Smaller community than EF/Dapper but well-regarded for high-throughput workloads.
- Raw Npgsql — direct ADO.NET against Postgres. The binary
COPYAPI (NpgsqlBinaryImporter) is unmatched for bulk inserts/exports. - EF Core 8/9/10 improvements —
AsNoTracking[WithIdentityResolution], compiled queries,ExecuteUpdateAsync/ExecuteDeleteAsync(no materialization),FromSqlInterpolatedas a first-class raw-SQL escape. Many historical "EF is slow" cases are addressed.
Decision¶
Hybrid, EF-first:
- EF Core is the default data-access layer for Molib at this stage — for CRUD, schema migrations, domain modeling, and the ordinary 80% of queries.
- Dapper will be introduced as an explicit escape hatch for hot paths once we have a measured bottleneck. We do not preemptively wire it in.
- Bulk import/export workloads will use Npgsql's binary
COPYAPI (NpgsqlBinaryImporter) directly — neither EF nor Dapper compete with it for that workload. This is expected to come into play once we have ingest/export features. - Linq2Db is acknowledged as a strong alternative to EF Core for projects where its tradeoffs (no tracking, lighter weight, LINQ-as-Arel ergonomics) fit better. Not adopted for Molib now, but kept in mind if EF Core proves the wrong shape later.
Consequences¶
Positive:
- Standard, well-trodden tooling for the immediate path; easy to onboard contributors.
- Migrations and schema evolution are first-class via EF Core's tooling.
- The hybrid approach defers complexity until it's justified by measurement, not speculation.
Negative / things to watch:
- EF entities must not leak across module boundaries — modules expose their own DTOs / domain types, so a future swap (to Dapper, Linq2Db, or raw Npgsql) is local in scope. This is a discipline cost.
- "EF is slow" decisions must be backed by a benchmark, not vibes. Common first fixes before reaching for Dapper:
AsNoTracking,ExecuteUpdate/Delete, projection to DTOs,FromSqlInterpolated, compiled queries. - Mixing data-access styles risks inconsistency. Document each Dapper / raw-SQL site with the reason it's not EF.
Triggers for revisiting¶
- A measured EF Core hot path that resists the standard tuning levers above → introduce Dapper for that endpoint.
- First import/export feature → wire up
NpgsqlBinaryImporter. - If EF Core's modeling ergonomics consistently fight Molib's domain (e.g. heavy use of joins-as-projections, set operations) → reassess Linq2Db.