Home C# / .NET Dapper vs EF Core: Choosing the Right ORM for Your .NET App
Intermediate 3 min · July 13, 2026

Dapper vs EF Core: Choosing the Right ORM for Your .NET App

Compare Dapper and Entity Framework Core for .NET applications.

N
Naren Founder & Principal Engineer

20+ years shipping production .NET services in enterprise systems. Everything here is grounded in real deployments.

Follow
Production
production tested
July 13, 2026
last updated
2,043
articles · all by Naren
Before you start⏱ 15-20 min read
  • Basic knowledge of C# and .NET
  • Familiarity with SQL and relational databases
  • Understanding of dependency injection and ASP.NET Core basics
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Dapper is a micro-ORM that gives you full SQL control with minimal overhead, ideal for high-performance scenarios.
  • EF Core is a full-featured ORM that automates data access with LINQ, change tracking, and migrations, best for complex business logic.
  • Choose Dapper for read-heavy, performance-critical systems; choose EF Core for rapid development with rich domain models.
  • Both can be mixed in the same project for different data access needs.
  • Your choice depends on team expertise, project complexity, and performance requirements.
✦ Definition~90s read
What is Dapper vs EF Core?

Dapper vs EF Core is a decision guide that helps you choose between a lightweight micro-ORM and a full-featured ORM for your .NET data access layer.

Think of Dapper as a bicycle – fast, simple, and you control every pedal stroke.
Plain-English First

Think of Dapper as a bicycle – fast, simple, and you control every pedal stroke. EF Core is like a self-driving car – it handles navigation, traffic, and parking for you, but it's heavier and you can't take shortcuts. For a quick trip to the store, the bike is perfect. For a cross-country road trip with family, the car is better.

Every .NET developer eventually faces the decision: Dapper or Entity Framework Core? Both are excellent data access technologies, but they serve different purposes. Dapper is a lightweight micro-ORM that maps query results to objects with minimal overhead. EF Core is a full Object-Relational Mapper that provides LINQ queries, change tracking, lazy loading, and migrations out of the box.

Choosing the wrong tool can lead to performance bottlenecks, over-engineered solutions, or maintenance nightmares. For example, using EF Core for a high-throughput reporting API might cause slow queries and memory bloat. Conversely, using Dapper for a complex CRUD application with many relationships could result in repetitive SQL and manual mapping.

In this guide, you'll learn the key differences, when to use each, and how to combine them effectively. We'll cover real-world scenarios, performance benchmarks, and production debugging techniques. By the end, you'll be able to make an informed decision for your next .NET project.

What is Dapper?

Dapper is a micro-ORM created by the Stack Overflow team. It extends IDbConnection with extension methods that execute SQL queries and map results to objects. Dapper is fast because it uses dynamic code generation to materialize objects, with minimal overhead. It gives you full control over SQL, making it ideal for performance-critical scenarios.

Key features
  • Execute raw SQL or stored procedures
  • Automatic object mapping
  • Support for async operations
  • Multi-mapping for relationships
  • Parameterized queries to prevent SQL injection

Dapper is not an ORM in the traditional sense; it doesn't track changes, manage relationships, or generate migrations. You write all SQL yourself.

DapperExample.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using System.Data.SqlClient;
using Dapper;

public class OrderRepository
{
    private readonly string _connectionString;

    public OrderRepository(string connectionString)
    {
        _connectionString = connectionString;
    }

    public async Task<IEnumerable<Order>> GetOrdersByCustomerAsync(int customerId)
    {
        using var connection = new SqlConnection(_connectionString);
        var sql = "SELECT * FROM Orders WHERE CustomerId = @CustomerId";
        return await connection.QueryAsync<Order>(sql, new { CustomerId = customerId });
    }

    public async Task<Order?> GetOrderWithDetailsAsync(int orderId)
    {
        using var connection = new SqlConnection(_connectionString);
        var sql = @"SELECT * FROM Orders WHERE Id = @Id;
                     SELECT * FROM LineItems WHERE OrderId = @Id;";
        using var multi = await connection.QueryMultipleAsync(sql, new { Id = orderId });
        var order = await multi.ReadSingleOrDefaultAsync<Order>();
        if (order != null)
        {
            order.LineItems = (await multi.ReadAsync<LineItem>()).ToList();
        }
        return order;
    }
}
💡Dapper Best Practice
📊 Production Insight
In production, Dapper's simplicity means fewer surprises. However, you must manage connections and transactions manually.
🎯 Key Takeaway
Dapper is a lightweight, high-performance micro-ORM that gives you full control over SQL.

What is Entity Framework Core?

Entity Framework Core (EF Core) is a full-featured ORM that provides LINQ queries, change tracking, lazy loading, eager loading, and migrations. It abstracts the database schema into a domain model, allowing you to work with objects rather than tables. EF Core is ideal for complex business applications where productivity and maintainability are key.

Key features
  • LINQ queries that translate to SQL
  • Change tracking for automatic updates
  • Lazy and eager loading of related data
  • Database migrations for schema evolution
  • Support for multiple database providers

EF Core can be slower than Dapper due to its abstraction layer, but it significantly reduces boilerplate code.

EFCoreExample.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using Microsoft.EntityFrameworkCore;

public class OrderContext : DbContext
{
    public DbSet<Order> Orders { get; set; }
    public DbSet<LineItem> LineItems { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("YourConnectionString");
    }
}

public class OrderService
{
    private readonly OrderContext _context;

    public OrderService(OrderContext context)
    {
        _context = context;
    }

    public async Task<List<Order>> GetOrdersByCustomerAsync(int customerId)
    {
        return await _context.Orders
            .Where(o => o.CustomerId == customerId)
            .ToListAsync();
    }

    public async Task<Order?> GetOrderWithDetailsAsync(int orderId)
    {
        return await _context.Orders
            .Include(o => o.LineItems)
            .FirstOrDefaultAsync(o => o.Id == orderId);
    }
}
🔥EF Core Performance Tip
📊 Production Insight
EF Core's LINQ abstraction can generate inefficient SQL if not carefully tuned. Always profile generated queries.
🎯 Key Takeaway
EF Core automates data access with LINQ and change tracking, boosting productivity for complex applications.

Performance Comparison: Dapper vs EF Core

Performance is often the deciding factor. Dapper is generally faster because it does less work. EF Core adds overhead for change tracking, query translation, and materialization. In benchmarks, Dapper can be 2-10x faster for simple queries, but the gap narrows for complex operations.

Consider a scenario: fetching 1000 orders with their line items. Dapper with a single SQL query and multi-mapping is extremely fast. EF Core with eager loading also performs well, but lazy loading would cause N+1 queries.

Memory usage: Dapper uses less memory because it doesn't track objects. EF Core's change tracking can consume significant memory for large result sets.

However, performance isn't everything. EF Core's productivity gains can outweigh speed differences for many applications.

PerformanceBenchmark.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using Dapper;
using Microsoft.EntityFrameworkCore;
using System.Data.SqlClient;

public class OrmBenchmark
{
    private readonly string _connectionString = "...";
    private readonly SqlConnection _connection;
    private readonly OrderContext _context;

    public OrmBenchmark()
    {
        _connection = new SqlConnection(_connectionString);
        _context = new OrderContext(new DbContextOptionsBuilder<OrderContext>()
            .UseSqlServer(_connectionString).Options);
    }

    [Benchmark]
    public async Task<List<Order>> DapperGetOrders()
    {
        using var conn = new SqlConnection(_connectionString);
        var orders = await conn.QueryAsync<Order>("SELECT * FROM Orders");
        return orders.ToList();
    }

    [Benchmark]
    public async Task<List<Order>> EFCoreGetOrders()
    {
        return await _context.Orders.AsNoTracking().ToListAsync();
    }
}

// Run: BenchmarkRunner.Run<OrmBenchmark>();
⚠ Benchmark Carefully
📊 Production Insight
In production, monitor query performance with tools like Application Insights or SQL Server Profiler.
🎯 Key Takeaway
Dapper is faster and uses less memory, but EF Core's productivity may be worth the trade-off.

When to Use Dapper

Dapper shines in scenarios where performance is critical and you need fine-grained control over SQL. Examples: - High-throughput APIs (e.g., real-time analytics) - Reporting and dashboards with complex queries - Microservices where each service has simple data access - Legacy databases with existing stored procedures - Read-heavy applications with minimal writes

Dapper is also great for teams that are comfortable with SQL and want to avoid ORM magic. It's easier to debug because you see exactly what SQL runs.

However, Dapper requires more manual work: writing SQL, mapping relationships, and managing transactions. It's not ideal for complex CRUD with many entities.

DapperComplexQuery.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public async Task<IEnumerable<SalesSummary>> GetSalesSummaryAsync(DateTime start, DateTime end)
{
    using var connection = new SqlConnection(_connectionString);
    var sql = @"
        SELECT 
            c.Name AS CustomerName,
            SUM(o.Total) AS TotalSales,
            COUNT(o.Id) AS OrderCount
        FROM Customers c
        INNER JOIN Orders o ON c.Id = o.CustomerId
        WHERE o.OrderDate BETWEEN @Start AND @End
        GROUP BY c.Name
        ORDER BY TotalSales DESC";
    return await connection.QueryAsync<SalesSummary>(sql, new { Start = start, End = end });
}
💡Dapper for Stored Procedures
📊 Production Insight
In production, Dapper's explicit SQL makes it easy to optimize and troubleshoot slow queries.
🎯 Key Takeaway
Use Dapper for performance-critical, read-heavy, or complex SQL scenarios.

When to Use EF Core

EF Core is ideal for applications with complex business logic, many entities, and frequent schema changes. Examples: - Line-of-business applications (e.g., ERP, CRM) - Applications with rich domain models and relationships - Projects where rapid development is prioritized - Teams that prefer LINQ over SQL - Applications that need automatic migrations

EF Core's change tracking simplifies updates: you modify objects and call SaveChangesAsync. Its LINQ provider allows you to write type-safe queries. Migrations enable version-controlled schema evolution.

However, EF Core can be slower and more memory-intensive. It also has a learning curve for advanced features like performance tuning.

EFCoreComplexQuery.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public async Task<List<SalesSummary>> GetSalesSummaryAsync(DateTime start, DateTime end)
{
    return await _context.Orders
        .Where(o => o.OrderDate >= start && o.OrderDate <= end)
        .GroupBy(o => o.Customer.Name)
        .Select(g => new SalesSummary
        {
            CustomerName = g.Key,
            TotalSales = g.Sum(o => o.Total),
            OrderCount = g.Count()
        })
        .OrderByDescending(s => s.TotalSales)
        .ToListAsync();
}
🔥EF Core Migrations
📊 Production Insight
In production, use EF Core's logging to capture generated SQL and identify performance issues early.
🎯 Key Takeaway
EF Core boosts productivity for complex applications with rich domain models and frequent schema changes.

Mixing Dapper and EF Core in the Same Project

You don't have to choose one exclusively. Many production applications use both: EF Core for complex CRUD operations and Dapper for performance-critical queries. This hybrid approach gives you the best of both worlds.

For example, use EF Core for your main business logic with change tracking and migrations. Use Dapper for read-only queries, reports, or bulk operations where performance matters.

To mix them, you need a shared connection string and possibly a transaction scope to ensure consistency. Here's a pattern:

HybridApproach.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class OrderService
{
    private readonly OrderContext _context;
    private readonly string _connectionString;

    public OrderService(OrderContext context, IConfiguration config)
    {
        _context = context;
        _connectionString = config.GetConnectionString("DefaultConnection");
    }

    public async Task<Order> CreateOrderAsync(Order order)
    {
        _context.Orders.Add(order);
        await _context.SaveChangesAsync();
        return order;
    }

    public async Task<IEnumerable<OrderSummary>> GetOrderSummariesAsync()
    {
        using var connection = new SqlConnection(_connectionString);
        var sql = "SELECT Id, CustomerName, Total FROM OrderSummaries";
        return await connection.QueryAsync<OrderSummary>(sql);
    }
}
⚠ Transaction Consistency
📊 Production Insight
In production, clearly separate read and write paths to avoid confusion and maintain consistency.
🎯 Key Takeaway
Mixing Dapper and EF Core allows you to optimize performance without sacrificing productivity.

Security Considerations

Both Dapper and EF Core protect against SQL injection when used correctly. Dapper's parameterized queries and EF Core's LINQ parameterization ensure user input is never concatenated into SQL.

However, there are pitfalls
  • Raw SQL in EF Core: If you use FromSqlRaw or ExecuteSqlRaw, you must parameterize inputs.
  • Dapper with dynamic SQL: Avoid building SQL strings with user input. Always use parameters.
  • Stored procedures: Both support them safely.

Additionally, consider connection string security. Store connection strings in secure configuration (e.g., Azure Key Vault, environment variables). Never hardcode credentials.

SecurityExample.csCSHARP
1
2
3
4
5
6
7
8
9
10
// Safe: parameterized query in Dapper
var sql = "SELECT * FROM Users WHERE Email = @Email";
var user = await connection.QuerySingleOrDefaultAsync<User>(sql, new { Email = userInput });

// Safe: LINQ in EF Core
var user = await _context.Users.FirstOrDefaultAsync(u => u.Email == userInput);

// Unsafe: string concatenation (never do this)
var sql = $"SELECT * FROM Users WHERE Email = '{userInput}'";
var user = await connection.QueryAsync<User>(sql); // SQL injection!
⚠ SQL Injection Risk
📊 Production Insight
In production, enable SQL injection detection in your web application firewall (WAF) as an extra layer.
🎯 Key Takeaway
Both ORMs are secure when used with parameterized queries. Avoid raw string concatenation.

Testing Strategies

Testing data access code is crucial. For Dapper, you can mock the IDbConnection or use an in-memory database. For EF Core, use the in-memory provider or a test database.

Dapper testing: Because Dapper extends IDbConnection, you can mock the connection and return expected results. However, it's easier to use a real database in integration tests.

EF Core testing: Use UseInMemoryDatabase for unit tests, but be aware it behaves differently from a real database. For reliable tests, use a real database like SQL Server LocalDB or a containerized database.

TestingExample.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// EF Core in-memory test
var options = new DbContextOptionsBuilder<OrderContext>()
    .UseInMemoryDatabase(databaseName: "TestDb")
    .Options;
using var context = new OrderContext(options);
context.Orders.Add(new Order { Id = 1, CustomerId = 1 });
context.SaveChanges();

// Dapper integration test (using real DB)
[Fact]
public async Task GetOrdersByCustomer_ReturnsOrders()
{
    using var connection = new SqlConnection(_connectionString);
    // Arrange: insert test data
    await connection.ExecuteAsync("INSERT INTO Orders (Id, CustomerId) VALUES (1, 1)");
    // Act
    var repo = new OrderRepository(_connectionString);
    var orders = await repo.GetOrdersByCustomerAsync(1);
    // Assert
    Assert.Single(orders);
}
💡Integration Tests
📊 Production Insight
In production, consider using database snapshots or transaction rollbacks to isolate tests.
🎯 Key Takeaway
Test data access code with integration tests against a real database for reliability.
● Production incidentPOST-MORTEMseverity: high

The Slow Dashboard: How EF Core's N+1 Query Brought Down a Production Report

Symptom
Users reported that the sales dashboard took over 30 seconds to load, timing out frequently.
Assumption
The developer assumed the database was slow and added more indexes.
Root cause
EF Core's lazy loading caused N+1 queries: one query for the list of orders and then a separate query for each order's line items.
Fix
Replaced lazy loading with eager loading using .Include() and switched to Dapper for the dashboard query, reducing load time to under 1 second.
Key lesson
  • Always monitor SQL queries generated by EF Core in production.
  • Use eager loading or explicit loading to avoid N+1 problems.
  • Consider Dapper for read-heavy, complex queries where you need full control.
  • Profile your application early to catch performance issues.
  • Don't assume database slowness; check your ORM's behavior first.
Production debug guideSymptom to Action4 entries
Symptom · 01
Slow page load or API response
Fix
Enable EF Core logging to capture SQL queries. Look for N+1 queries or missing indexes.
Symptom · 02
High memory usage
Fix
Check if change tracking is enabled for read-only queries. Use .AsNoTracking() in EF Core.
Symptom · 03
Deadlocks or timeouts
Fix
Review transaction scopes and ensure proper use of async/await. Consider using Dapper for long-running queries.
Symptom · 04
Inconsistent data
Fix
Verify that Dapper queries handle transactions correctly. Use SqlConnection with explicit transactions.
★ Quick Debug Cheat SheetCommon ORM issues and immediate actions
N+1 queries in EF Core
Immediate action
Add `.Include()` or `.ThenInclude()` to eager load related data.
Commands
var orders = context.Orders.Include(o => o.LineItems).ToList();
Enable logging: `optionsBuilder.LogTo(Console.WriteLine);`
Fix now
Switch to Dapper for the specific query.
Slow Dapper query+
Immediate action
Check SQL execution plan in SSMS.
Commands
SELECT * FROM Orders WHERE ... -- run in SSMS
Add indexes or rewrite query.
Fix now
Use QueryAsync with buffered=false for large result sets.
Memory leak with EF Core+
Immediate action
Use short-lived DbContext instances.
Commands
using var context = new MyDbContext();
Disable change tracking: `context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;`
Fix now
Switch to Dapper for read-only operations.
FeatureDapperEF Core
TypeMicro-ORMFull ORM
SQL ControlFull controlAbstracted via LINQ
PerformanceHighModerate
Change TrackingNoYes
MigrationsNoYes
Learning CurveLowMedium-High
Async SupportYesYes
Stored ProceduresExcellentGood
Best ForPerformance-critical, read-heavyComplex CRUD, rapid development
⚙ Quick Reference
8 commands from this guide
FileCommand / CodePurpose
DapperExample.csusing System.Data.SqlClient;What is Dapper?
EFCoreExample.csusing Microsoft.EntityFrameworkCore;What is Entity Framework Core?
PerformanceBenchmark.csusing BenchmarkDotNet.Attributes;Performance Comparison
DapperComplexQuery.cspublic async Task> GetSalesSummaryAsync(DateTime start...When to Use Dapper
EFCoreComplexQuery.cspublic async Task> GetSalesSummaryAsync(DateTime start, DateT...When to Use EF Core
HybridApproach.cspublic class OrderServiceMixing Dapper and EF Core in the Same Project
SecurityExample.csvar sql = "SELECT * FROM Users WHERE Email = @Email";Security Considerations
TestingExample.csvar options = new DbContextOptionsBuilder()Testing Strategies

Key takeaways

1
Dapper is best for performance-critical, read-heavy scenarios with full SQL control.
2
EF Core excels in complex applications with rich domain models and frequent schema changes.
3
You can mix both in the same project to leverage their strengths.
4
Always use parameterized queries to prevent SQL injection.
5
Profile and monitor your data access code in production to catch performance issues.

Common mistakes to avoid

3 patterns
×

Using EF Core for read-only queries without AsNoTracking

×

Concatenating user input into Dapper SQL strings

×

Not disposing DbContext or SqlConnection properly

INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
Explain the difference between Dapper and Entity Framework Core.
Q02SENIOR
How would you handle N+1 query problem in EF Core?
Q03SENIOR
Describe a scenario where you would choose Dapper over EF Core and vice ...
Q01 of 03JUNIOR

Explain the difference between Dapper and Entity Framework Core.

ANSWER
Dapper is a micro-ORM that provides fast object mapping for raw SQL queries. EF Core is a full ORM with LINQ, change tracking, and migrations. Dapper gives you more control and performance, while EF Core boosts productivity.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
Can I use Dapper and EF Core together in the same project?
02
Which is faster, Dapper or EF Core?
03
Does Dapper support async operations?
04
Does EF Core support stored procedures?
05
Which ORM should I use for a new microservice?
N
Naren Founder & Principal Engineer

20+ years shipping production .NET services in enterprise systems. Everything here is grounded in real deployments.

Follow
Verified
production tested
July 13, 2026
last updated
2,043
articles · all by Naren
🔥

That's ASP.NET. Mark it forged?

3 min read · try the examples if you haven't

Previous
Dapper Micro-ORM in .NET
32 / 35 · ASP.NET
Next
Introduction to .NET MAUI