Performance & Tech-Debt Focused Developers
Dapper vs Entity Framework
Dapper vs Entity Framework

Dapper vs Entity Framework

After two decades in software engineering, I’ve seen a fair number of ORMs come and go, and I’ve worked with everything from ADO.NET to Entity Framework. Lately, I’ve been spending time with Dapper. It’s described as a micro-ORM, and I’ve come to appreciate what it does and what it avoids. It’s lean, fast, and doesn’t pretend to be anything more than a lightweight tool to simplify raw SQL execution. In contrast, Entity Framework (EF) aims to abstract data access (almost) entirely, providing a higher, object-oriented way to interact with your database. Both have their place; choosing between them usually comes down to the project’s needs.

Performance is one of Dapper’s biggest draws. EF has improved significantly over the years, but Dapper remains faster in most scenarios—especially read-heavy operations where you’re just mapping rows to objects. Dapper’s performance is near ADO.NET because that’s essentially what it wraps. There’s no change tracking, no LINQ translation, no extra plumbing. This speed comes with trade-offs: You take on more responsibility, particularly around SQL maintenance and guarding against injection.

Dapper’s syntax is straightforward for basic operations. For example, a simple query looks like this:
var users = connection.Query("SELECT * FROM Users WHERE IsActive = @IsActive", new { IsActive = true });
This is clean, and readable. You can see exactly what SQL is being run, which makes debugging easier. But as soon as you start dealing with joins, dynamic queries, or bulk operations, things get messy. For example, if you want to map parent-child relationships using QueryMultiple or Query with custom mappers, the simplicity starts to erode:


var sql = @"SELECT * FROM Orders o
INNER JOIN OrderItems i ON o.Id = i.OrderId
WHERE o.CustomerId = @CustomerId";

var orderDict = new Dictionary();
connection.Query(
sql,
(order, item) =>
{
if (!orderDict.TryGetValue(order.Id, out var currentOrder))
{
currentOrder = order;
currentOrder.Items = new List();
orderDict.Add(currentOrder.Id, currentOrder);
}
currentOrder.Items.Add(item);
return currentOrder;
},
new { CustomerId = 42 },
splitOn: "Id"
);

It works, but it’s a far cry from EF’s automatic property handling.

One quirk worth noting is Dapper’s sensitivity to parameter order, especially when using anonymous objects or dynamic parameters. If you’re not careful, mismatched names or ordering issues can result in bugs that are hard to trace. EF, on the other hand, handles this internally with its LINQ abstraction, so you’re less likely to run into those kinds of issues at the cost of transparency and sometimes performance.