/**/

EF Core vs Dapper: Stop Arguing and Just Use Both

4 min readAjay Patel

If you hang around .NET forums long enough, you will eventually stumble into a holy war: Entity Framework Core vs. Dapper.

The Dapper crowd argues that EF Core is a bloated, slow abstraction that generates horrific SQL queries. The EF Core crowd argues that Dapper requires you to write fragile magic strings and manage your own database migrations like a caveman.

I’ve built massive enterprise systems using both. And the truth is, picking one over the other is a false dichotomy. The absolute best way to build a high-performance .NET application is to use them both.

Here is how I architect my data access layer to get the best of both worlds.

The Golden Rule: EF for Writes, Dapper for Reads

This pattern is often referred to as CQRS (Command Query Responsibility Segregation). You don't need a massive MediatR setup to achieve this; you just need to separate how you write data from how you read data.

Why EF Core is King of Writes (Commands)

Entity Framework Core's Change Tracker is brilliant. When you need to load a complex object graph, modify a few fields, and save it back to the database, EF Core handles the transaction and the UPDATE statements flawlessly.

// Modifying complex data is trivial in EF Core
var order = await _db.Orders
    .Include(o => o.LineItems)
    .FirstOrDefaultAsync(o => o.Id == orderId);

order.Status = OrderStatus.Shipped;
order.LineItems.First().ShippedDate = DateTime.UtcNow;

// EF Core figures out the exact SQL needed.
await _db.SaveChangesAsync(); 

Trying to do this in Dapper requires writing complex multi-mapping queries, manually tracking which fields changed, and writing custom UPDATE statements. It's tedious and error-prone.

Why Dapper is King of Reads (Queries)

When you are displaying a dashboard to a user, you rarely need a fully tracked domain entity. You just need a flattened DTO (Data Transfer Object).

EF Core struggles here. Even with .AsNoTracking(), EF Core has overhead translating LINQ to SQL, mapping results, and dealing with complex joins.

Dapper, being a micro-ORM, executes raw SQL and maps it directly to a C# object with almost zero overhead. It is blisteringly fast.

// Dapper for a complex read query
var sql = @"
    SELECT 
        o.Id, 
        o.OrderDate, 
        c.Name AS CustomerName,
        SUM(i.Price * i.Quantity) AS TotalAmount
    FROM Orders o
    INNER JOIN Customers c ON o.CustomerId = c.Id
    INNER JOIN LineItems i ON o.Id = i.OrderId
    WHERE o.Status = @Status
    GROUP BY o.Id, o.OrderDate, c.Name";

using var connection = new SqlConnection(_connectionString);
var dashboardData = await connection.QueryAsync<OrderDashboardDto>(sql, new { Status = "Shipped" });

Writing that specific GROUP BY query in EF Core LINQ is a nightmare. It often generates inefficient SQL that pulls back way more columns than you actually need. With Dapper, you have absolute control over the execution plan.

They Share the Same Connection

The beauty of this approach is that EF Core and Dapper play very nicely together. Dapper extends the IDbConnection interface. Because EF Core's DbContext manages a database connection internally, you can actually pull the connection out of EF Core and pass it directly to Dapper!

// Using EF Core and Dapper in the exact same transaction!
var connection = _db.Database.GetDbConnection();

// Execute a Dapper query using the EF Core connection
var users = await connection.QueryAsync<UserDto>("SELECT Id, Name FROM Users");

The Verdict

Stop treating data access as a zero-sum game.

Use Entity Framework Core for:

  • Database Migrations
  • Adding, Updating, and Deleting records
  • Enforcing Domain-driven design constraints

Use Dapper for:

  • Complex reporting queries
  • High-throughput read-heavy endpoints
  • Times when LINQ generates terrible SQL

By combining them, you get the rapid development speed of an ORM where it counts, and the raw bare-metal performance of raw SQL where you need it.

About the Author

Ajay Patel

Software Developer specializing in .NET, C#, and ASP.NET Core. Built .NET Toolbox to give developers privacy-first, browser-based utilities for their daily workflow.

Read more about Ajay