What Is Dapper?
Dapper is classed as a micro ORM. That means it is still an Object–Relational Mapper, but it is intentionally lightweight, opinionated, and minimal.
Unlike full-featured ORMs such as Entity Framework or NHibernate, Dapper focuses on performance and control, providing just enough abstraction to map query results to objects—without hiding SQL from the developer.
Put simply:
Dapper executes SQL you write, maps the results to POCOs, and gets out of the way.
It does not:
- Generate SQL
- Track entities
- Manage change detection
- Enforce a domain model
And that is precisely why it is fast and predictable.
Why Dapper?
The primary reason teams choose Dapper over Entity Framework is control.
With EF, it’s easy to end up with:
- Complex LINQ queries
- Unpredictable SQL generation
- Poor execution plans
- Surprises that only show up in production
With Dapper:
- You write the SQL
- You know exactly what runs
- DBAs can review and optimise queries
- Performance issues are visible and diagnosable
--Example: fetching a single column by ID
SELECT EventName
FROM Events
WHERE Id = @Id
There is no translation layer, no expression tree, and no guesswork.
When You Should Not Use Dapper
There are legitimate cases where Dapper is the wrong tool.
1. Heavy reliance on Entity Framework features
If your existing application depends on:
- Change tracking
- Lazy loading
- Cascade updates
- EF-specific behaviours
then switching to Dapper will be painful.
Dapper does no entity tracking. It works with plain POCOs. If you expect “EF magic”, you will be disappointed.
2. Teams that cannot or will not write SQL
- This is uncomfortable to say, but it’s real.
- Junior developers often resist Dapper because:
- They must write SQL
- They can’t hide behind LINQ
- Mistakes are more visible
If your team is still learning ASP.NET and C#, adding SQL into the mix may slow them down initially.
That said: if you work with databases professionally, you should know SQL. Avoiding it only delays the problem.
Who came up with Dapper?
Dapper was created by the team at Stack Overflow.
They built it to solve a real production problem:
- LINQ-to-SQL and early ORMs were consuming excessive CPU
- SQL generation was opaque and inefficient
- Scaling web servers became difficult
Dapper was introduced to:
- Reduce overhead
- Eliminate SQL generation costs
- Improve predictability under load
It has since become one of the most widely used data access libraries in .NET.
What Dapper Actually Is
Dapper is:
- A simple object mapper for .NET
- Often called the “King of Micro ORMs” for speed
- An extension over ADO.NET
- At a high level, working with Dapper involves three steps:
- Create an IDbConnection
- Write SQL for CRUD operations
- Execute the SQL using Dapper extension methods
Dapper supports:
- SQL Server
- PostgreSQL
- MySQL
- Oracle
- SQLite
Core Dapper Extension Methods
Dapper extends IDbConnection with the following commonly used methods:
- – Execute
- – Executes a command and returns affected row count
- – Query
- – Executes a query and maps results to objects
- – QueryFirst
- – Returns the first row (throws if none)
- – QueryFirstOrDefault
- – Returns first row or default
- – QuerySingle
- – Expects exactly one row
- – QuerySingleOrDefault
- – One or zero rows only
- – QueryMultiple
- – Reads multiple result sets in a single round trip
Dapper supports both synchronous and asynchronous APIs. In modern .NET (6+), you should default to async.
Using Dapper in ASP.NET Web API (.NET 6+)
//Packages Needed
dotnet add package Dapper
dotnet add package Microsoft.Data.SqlClient
DTO
public sealed class EventDto
{
public int Id { get; init; }
public string EventName { get; init; } = string.Empty;
}
Interface
public interface IEventRepository
{
Task<EventDto?> GetByIdAsync(int id);
}
Interface*
public interface IEventRepository
{
Task<EventDto?> GetByIdAsync(int id);
}
//Repository Using Dapper
public sealed class EventRepository : IEventRepository
{
private readonly IDbConnection _connection;
public EventRepository(IDbConnection connection)
{
_connection = connection;
}
public async Task<EventDto?> GetByIdAsync(int id)
{
const string sql = """
SELECT Id, EventName
FROM Events
WHERE Id = @Id
""";
return await _connection.QuerySingleOrDefaultAsync<EventDto>(
sql,
new { Id = id }
);
}
}
Dependency Injection (Program.cs)
builder.Services.AddScoped<IDbConnection>(_ =>
new SqlConnection(builder.Configuration.GetConnectionString("Default"))
);
builder.Services.AddScoped<IEventRepository, EventRepository>();
This keeps:
- Connection lifetime scoped per request
- SQL explicit
- Dapper isolated behind repositories
Web API Endpoint
[ApiController]
[Route("api/events")]
public sealed class EventsController : ControllerBase
{
private readonly IEventRepository _repository;
public EventsController(IEventRepository repository)
{
_repository = repository;
}
[HttpGet("{id:int}")]
public async Task<IActionResult> Get(int id)
{
var result = await _repository.GetByIdAsync(id);
return result is null ? NotFound() : Ok(result);
}
}
Repository Pattern: Reality Check
Let’s be blunt.
- Generic repositories + Unit of Work are an anti-pattern in most real systems
- They add abstraction without adding clarity
-
They actively hide performance problems
A repository should: -
Work with one aggregate
-
Express business intent
-
Use Dapper internally
-
Own its transaction boundary (per database)
If multiple aggregates must change together:
- Your business process is the unit of work
- Use domain events
- Make handlers idempotent
- Avoid distributed transactions
Dapper does not dictate architecture. Bad architecture with Dapper is still bad architecture.
Where Dapper Shines (Real Scenarios)
This is where Dapper earns its keep—especially in legacy systems.
- LINQ-to-SQL or EF choking on large datasets
- Reporting queries
- Historical data
- Analytics
- Batch processing
- Legacy WebForms / ASP.NET MVC apps
- EF 4.x / LINQ-to-SQL performance issues
- Gradual migration strategy
- Co-existence with existing ORM
- Stored-procedure-heavy systems
- Dapper maps SP results cleanly
- No fighting EF abstractions
- High-throughput APIs
- Reduced allocations
- No tracking overhead
- Predictable SQL execution
- Read-heavy CQRS read models
- Dapper for reads
- EF or other tools for writes (hybrid approach)
Final Take
Dapper is not a silver bullet.
It will not save bad SQL, bad schema design, or bad architecture.
But if:
- You care about performance
- You want control
- You work with legacy systems
- You need predictability at scale
Then Dapper is one of the most reliable tools in the .NET ecosystem.