Skip to content

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 COPY API (NpgsqlBinaryImporter) is unmatched for bulk inserts/exports.
  • EF Core 8/9/10 improvementsAsNoTracking[WithIdentityResolution], compiled queries, ExecuteUpdateAsync/ExecuteDeleteAsync (no materialization), FromSqlInterpolated as a first-class raw-SQL escape. Many historical "EF is slow" cases are addressed.

Decision

Hybrid, EF-first:

  1. 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.
  2. 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.
  3. Bulk import/export workloads will use Npgsql's binary COPY API (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.
  4. 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.