Design & Architecture

Dapper

Intermediate

Dapper is a thin, fast micro-ORM for .NET: you write the SQL, Dapper handles parameterisation and maps the result rows to your types. It gives you near-hand-written performance and total control over the query, with none of the change-tracking machinery of a full ORM. We use it where read performance and SQL control matter — typically the query side — alongside EF Core on the write side.

Where EF Core generates SQL for you, Dapper expects you to provide it and simply removes the boilerplate of `DbCommand`, parameters, and `DataReader`-to-object mapping. There is no change tracker, no lazy loading, no hidden query generation — what you write is what runs. That makes it fast and predictable, and ideal for read-heavy, performance-sensitive, or SQL-shaped work.

The trade-off is that you own the SQL: its correctness, its parameterisation, and its maintenance. Dapper will faithfully execute an injectable query if you build one by string concatenation, so the discipline around input is on you. A common and healthy pattern is CQRS-shaped: EF Core for commands (rich domain writes), Dapper for queries (fast, flat read models). See CQRS, SQL Performance Tuning, and Trust Boundaries.

Always parameterise

Interpolated SQL — injectable var sql = $"SELECT * FROM Customers WHERE Email = '{email}'";
var rows = conn.Query(sql);
// email = "x' OR '1'='1" -> returns every customer

User input is concatenated into the query text. An attacker rewrites the WHERE clause and reads the whole table. Dapper executes it faithfully.

Parameterised query const string sql = "SELECT Id, Email, RiskLevel FROM Customers WHERE Email = @Email";
var rows = await conn.QueryAsync(sql, new { Email = email });

The input is bound as a parameter — never part of the SQL text — so injection is impossible, the plan is cached, and only the needed columns are read.

Use it where it fits

Connections, async, and resilience

Self-review checklist

Why it matters: Dapper gives you the speed and control of hand-written SQL without the boilerplate, which makes it the right tool for read-heavy and performance-critical paths — especially as the query side of a CQRS split with EF Core on the writes. But owning the SQL means owning its safety: parameterise everything, dispose connections, and keep transactions explicit. Get that discipline right and Dapper is fast and predictable; get parameterisation wrong and you have handed an attacker your database.