What it is
Dapper is a micro-ORM that maps SQL results onto objects with almost no overhead, leaving the SQL entirely under your control.
Extension methods on IDbConnection execute SQL and map rows to types. Parameters are passed as anonymous objects and always parameterised.
Installation
dotnet add package DapperGetting started
The smallest useful thing you can do with it, and what each part means.
csharp
using var conn = new SqlConnection(connectionString);
var book = await conn.QuerySingleOrDefaultAsync<Book>(
"SELECT Id, Title, Year FROM Books WHERE Id = @Id",
new { Id = 42 });
var recent = await conn.QueryAsync<Book>(
"SELECT Id, Title, Year FROM Books WHERE Year >= @Year",
new { Year = 1990 });
var rows = await conn.ExecuteAsync(
"UPDATE Books SET Year = @Year WHERE Id = @Id",
new { Year = 1966, Id = 42 });csharp
var sql = @"SELECT b.Id, b.Title, a.Id, a.Name
FROM Books b JOIN Authors a ON a.Id = b.AuthorId";
var books = await conn.QueryAsync<Book, Author, Book>(
sql,
(book, author) => { book.Author = author; return book; },
splitOn: "Id"); // where the second object's columns beginAdvanced usage
Where the library earns its place over a simpler alternative.
csharp
// One round trip, several result sets.
using var multi = await conn.QueryMultipleAsync(@"
SELECT * FROM Books WHERE Id = @Id;
SELECT * FROM Reviews WHERE BookId = @Id;", new { Id = 42 });
var book = await multi.ReadSingleAsync<Book>();
var reviews = (await multi.ReadAsync<Review>()).ToList();
// Passing a collection executes the statement once per item.
await conn.ExecuteAsync(
"INSERT INTO Books (Title, Year) VALUES (@Title, @Year)",
booksToInsert); // batched, but still one statement eachErrors and fixes
The failures you are most likely to hit, and what actually resolves them.
- When using the multi-mapping APIs ensure you set the splitOn parameter
- Dapper cannot infer the boundary. Set splitOn to the first column name of the second object.
- Properties are null after a query
- Column names do not match property names. Alias them in the SQL, or enable MatchNamesWithUnderscores for snake_case columns.
Best practices
- Always pass parameters as an object; never interpolate values into the SQL string.
- Get splitOn right when multi-mapping, and verify the result rather than assuming.
- Use Dapper for read-heavy hot paths and EF Core where change tracking and migrations help.
- Let the connection pool work — create and dispose connections per operation rather than holding one open.
Background
Why it exists, and what it was reacting to.
Built by the Stack Overflow team for their own hot paths, Dapper exists because full ORMs added measurable latency to their highest-traffic queries. It does one thing: turn a result set into objects, quickly.
