Use EF Core when you want a full ORM that boosts productivity and long-term maintainability (LINQ, change tracking, migrations, cross-database providers). Use Dapper when you want maximum SQL control and minimal overhead, especially for read-heavy or performance-critical endpoints. If you’re building a large system, a hybrid approach (using EF Core for writes and Dapper for reads) often provides the best balance.
How do you choose between EF Core and Dapper in 60 seconds
Choose EF Core if most of these are true:
- You’re building CRUD-heavy business features.
- You want LINQ and strongly typed query composition.
- You benefit from change tracking and unit-of-work patterns.
- You want migrations and schema evolution built in (Microsoft Learn).
- You want easy provider switching across databases (Microsoft Learn).
Choose Dapper if most of these are true:
- You need handwritten SQL for complex joins, common table expressions (CTEs), or reporting.
- You’re optimizing a hot path where overhead matters.
- Your queries return data transfer objects (DTOs) or projections, not domain aggregates.
- You want SQL that’s transparent to debug and tune.
Choose hybrid (EF Core and Dapper) if:
- Writes benefit from EF Core’s tracking and transactions.
- Reads need fast projections and SQL control (dashboards, search, reporting).
- You’re already doing command query responsibility segregation (CQRS)-style separation, even informally.
What is EF Core and why does it exist
Entity Framework Core (EF Core) is Microsoft’s modern, cross-platform, open-source ORM for .NET that lets you work with a database using .NET objects and reduces repetitive data-access code. (Microsoft Learn)
At its core, EF Core exists to help developers program against a conceptual model instead of writing and maintaining large amounts of database plumbing manually—a goal that historically shaped Entity Framework as a concept. (Microsoft Learn)
What is Dapper and why does it exist
Dapper is a simple object mapper for .NET that focuses on executing SQL and efficiently mapping results to objects with minimal abstraction. (GitHub)
It exists to keep data access fast, explicit, and SQL-first, especially when teams want to avoid the overhead and “magic” of a full ORM.
What does ORM vs. micro ORM mean in practice
An ORM like EF Core typically provides:
- Query composition (LINQ).
- Relationship handling.
- Change tracking and unit-of-work behavior. (Microsoft Learn)
- Migrations and schema management. (Microsoft Learn)
- A provider model across databases. (Microsoft Learn)
A micro ORM like Dapper typically provides:
- SQL execution helpers.
- Fast mapping from rows to objects.
- Minimal overhead and minimal “model management.”
Mental model of EF Core vs. Dapper
- EF Core is a full-service kitchen: you place an order (LINQ/model), it cooks (SQL translation), serves (materialization), and cleans up (tracking).
- Dapper is your own kitchen: you cook the SQL yourself, and Dapper helps plate it efficiently.
How does EF Core map data and track changes
EF Core is centered around a DbContext, which represents a session with the database and enables querying and saving. (Microsoft Learn)
When queries return entity types, EF Core tracks entities by default, keeping state in a change tracker so modifications can be persisted on SaveChanges. (Microsoft Learn)
Simplified procedure (text diagram):

EF Core explicitly passes a representation of the LINQ query to the provider, and the provider translates it to the database-specific language, such as SQL. (Microsoft Learn)
How does Dapper map query results to objects
Dapper is intentionally thin and performs very little work beyond executing SQL and mapping result rows to objects. This makes its runtime behavior predictable and its performance characteristics easy to diagnose under load. It works like this:
- You write the SQL.
- Dapper executes it using ADO.NET.
- Each result row is mapped directly to an object or DTO by matching column names to properties.
There is no change tracking, relationship fix-up, or schema model. As stated on its GitHub page, Dapper’s focus is being a “simple object mapper.”
What EF Core features matter most in real systems
These are the features that usually drive teams toward EF Core for core business logic.
How do DbContext and DbSet shape your architecture
A quick explanation of how EF Core “wants” you to structure data access.
EF Core uses a model made up of entity classes and a context object (DbContext) representing a database session. (Microsoft Learn)
This naturally fits:
- Unit-of-work patterns.
- Aggregate-centric domain modeling.
- Transactional write workflows.
Why do migrations change how teams manage schema
Migrations are a workflow decision as much as a feature.
EF Core migrations are source-controlled changes generated by comparing the current model to a previous snapshot, and EF Core records applied migrations in a history table. (Microsoft Learn)
This makes schema evolution part of normal development, not a separate manual process.
How do lazy loading and eager loading affect performance
Loading strategy is one of the most common sources of “EF is slow” incidents.
EF Core supports lazy loading via proxies, but it can cause extra database roundtrips (the classic N+1 problem). (Microsoft Learn)
That’s why many production teams prefer explicit eager loading for predictable query shapes.
What Dapper features matter most in real systems
These are the features that usually drive teams toward Dapper for read paths and reporting.
Why does SQL-first data access improve debugging and tuning
When the query is the product, SQL-first often wins.
Because you own the SQL, you can:
- Copy and paste queries into your DB console.
- Tune indexes and query plans directly.
- Use database-specific features (CTEs, window functions, and hints where appropriate).
Why does no change tracking change your write strategy
This is where micro ORM simplicity can become a cost.
Dapper doesn’t track entity state, so writes are explicit:
- You decide when to INSERT and UPDATE.
- You manage concurrency patterns (row version/time stamps, etc.)
- You build your own unit-of-work behavior (if needed).
EF Core vs. Dapper: Feature comparison table
| Category | EF Core (Full ORM) | Dapper (Micro ORM) |
|---|---|---|
| Query style | LINQ translated to SQL (Microsoft Learn) | Raw SQL you write (GitHub) |
| Change tracking | Yes by default (Microsoft Learn) | No |
| Migrations | Built-in workflow (Microsoft Learn) | No |
| Loading related data | Supports strategies; lazy loading has N+1 risk (Microsoft Learn) | Manual joins and multimapping |
| Cross-database support | Provider model (Microsoft Learn) | Depends on ADO.NET provider and your SQL dialect |
| Best fit | CRUD and domain modeling | Read-heavy and complex SQL/projections |
| Debugging SQL | Often requires logging and inspection | Direct—SQL is already explicit |
Where performance differences really come from
This section explains performance in terms of framework overhead, query shape, and database behavior.
When does EF Core overhead matter
EF Core can be fast, but features have costs.
EF Core tracking keeps entity instances in the change tracker, and no-tracking queries are generally quicker in read-only scenarios because there’s no need to set up tracking info. (Microsoft Learn)
EF Core overhead tends to show up when:
- You load lots of entities into memory.
- You do high-frequency read paths with tracking on.
- You rely on lazy loading and trigger many roundtrips. (Microsoft Learn)
When does Dapper win (and when is it irrelevant)
Dapper’s gains are biggest when your app is CPU- or alloc-heavy, not DB-bound.
Dapper often shines when:
- You return lightweight DTO projections.
- You run many short queries per second.
- You avoid extra allocations and tracking.
But if your bottleneck is:
- network latency
- slow queries or missing indexes
- lock contention
…then switching ORMs won’t save you.
Code: Simple SELECT with EF Core vs. Dapper
Side-by-side examples make the difference in abstraction level obvious.
EF Core (LINQ and optional no-tracking)
C#
// Entities: User { Id, FirstName, LastName, IsActive }
var users = await db.Users
.AsNoTracking() // best default for read-only lists
.Where(u => u.IsActive)
.OrderBy(u => u.LastName)
.Select(u => new { u.Id, u.FirstName, u.LastName }) //projection helps performance
.ToListAsync();
EF Core’s tracking versus no-tracking behavior is an explicit lever, and no-tracking is typically preferred for read-only queries. (Microsoft Learn)
Dapper (SQL and parameters)
C#
const string sql = """
SELECT Id, FirstName, LastName
FROM Users
WHERE IsActive = @IsActive
ORDER BY LastName;
""";
var users = await connection.QueryAsync(
sql,
new { IsActive = true }
);
public sealed record UserListItem(int Id, string FirstName, string LastName);
Code: Insert/update with EF Core vs. Dapper
Writes are where EF Core’s unit-of-work model changes day-to-day productivity.
EF Core (Tracked entity + SaveChanges)
C#
db.Users.Add(new User
{
FirstName = "John",
LastName = "V",
IsActive = true
});
await db.SaveChangesAsync();
Dapper (explicit SQL)
C#
const string insertSql = """
INSERT INTO Users (FirstName, LastName, IsActive)
VALUES (@FirstName, @LastName, @IsActive);
""";
await connection.ExecuteAsync(insertSql, new
{
FirstName = "John",
LastName = "V",
IsActive = true
});
Code: Performance-focused query patterns you can actually use
Practical techniques that help more than “switch ORM” does.
How do you speed up hot EF Core read paths with compiled queries
Compiled queries can reduce repeated translation overhead on frequently executed queries.
EF Core provides EF.CompileQuery to create a compiled query delegate. (Microsoft Learn)
C#
private static readonly Func> ActiveUsersQuery =
EF.CompileAsyncQuery((AppDbContext db, bool isActive) =>
db.Users
.AsNoTracking()
.Where(u => u.IsActive == isActive)
.OrderBy(u => u.LastName)
.Select(u => new UserListItem(u.Id, u.FirstName, u.LastName)));
public async Task> GetActiveUsersAsync(bool isActive, CancellationToken ct)
{
var result = new List();
await foreach (var item in ActiveUsersQuery(db, isActive).WithCancellation(ct))
result.Add(item);
return result;
}
How do you keep Dapper fast and safe for hot endpoints
The “easy win” is correct parameterization and tight projections.
C#
const string sql = """
SELECT Id, FirstName, LastName
FROM Users
WHERE IsActive = @IsActive AND LastName >= @Start
ORDER BY LastName
OFFSET @Skip ROWS FETCH NEXT @Take ROWS ONLY;
""";
var page = await connection.QueryAsync(sql, new
{
IsActive = true,
Start = "M",
Skip = 0,
Take = 50
});
Which tool fits common .NET application types
The following table is a scenario-based mapping that developers and architects can use during design review.
| Workload | EF Core Fit | Dapper Fit | Why |
|---|---|---|---|
| CRUD-heavy business apps | Excellent | Limited | EF Core reduces boilerplate and supports tracking and migrations (Microsoft Learn) |
| Read-heavy APIs | Good | Excellent | Dapper excels at fast projections and SQL control |
| Reporting and analytics | Limited | Excellent | Hand‑tuned SQL and CTEs often matter more than entity modeling |
| Enterprise apps (long-lived) | Excellent | Good | EF Core improves consistency; hybrid is often best |
| Microservices | Good | Good | Choose per service: EF for write domain, Dapper for query services |
Choose per service: EF for write domain, Dapper for query services
When does a hybrid EF Core + Dapper approach make sense
A hybrid EF Core–Dapper approach is commonly used in larger systems that need strong transactional guarantees for writes and high-performance, predictable reads.
This approach is inspired by the core idea of Command Query Responsibility Segregation (CQRS), which separates data-modifying operations (commands) from data-retrieval operations (queries). However, it does not implement full CQRS. Instead, it applies the principle in a pragmatic way, without introducing additional architectural complexity, such as command/query handler pipelines, message buses, separate read/write models, or event sourcing.
A hybrid approach is usually justified when:
- The write model benefits from EF Core, including change tracking, relationship management, and transactional workflows. (Microsoft Learn)
- The read model benefits from explicit SQL, optimized projections, and high-throughput query execution.
- Schema evolution is managed through EF Core migrations, keeping database changes centralized. (Microsoft Learn)
Simplified data access flow (CQRS-inspired)

What this diagram shows:
- EF Core is used for database writes, where tracking and transactions are valuable.
- Dapper is used for database reads, where explicit SQL and minimal overhead provide better performance.
- Both reads and writes operate against the same database.
- This separation reflects CQRS principles without the full CQRS infrastructure.
How to implement EF Core for writes and Dapper for reads in an ASP.NET Core app
Straightforward setup with safe connection/transaction handling patterns.
Step 1: Keep EF Core as your primary unit of work
Use DbContext for write workflows and schema management:
- Keep writes in an application service or command handler.
- Use migrations for schema changes. (Microsoft Learn)
Step 2: Add a query service that uses Dapper for projections
Make reads explicit, DTO-oriented, and easy to tune.
C#
public sealed class UserReadService
{
private readonly AppDbContext _db;
public UserReadService(AppDbContext db) => _db = db;
public async Task> SearchUsersAsync(string term, CancellationToken ct)
{
var conn = _db.Database.GetDbConnection();
const string sql = """
SELECT TOP (50) Id, FirstName, LastName
FROM Users
WHERE LastName LIKE @Term OR FirstName LIKE @Term
ORDER BY LastName;
""";
// Ensure connection is open (EF opens it for its own operations; be explicit here)
if (conn.State != System.Data.ConnectionState.Open)
await conn.OpenAsync(ct);
var rows = await conn.QueryAsync(sql, new { Term = $"%{term}%" });
return rows.AsList();
}
}
Step 3: Share a transaction when you truly need read-your-writes
Only do this when required; otherwise keep reads and writes separate for simplicity.
In EF Core, you can start a transaction via DbContext.Database. Then you can pass the underlying DbTransaction to Dapper so that both participate in the same transaction boundary.
Note: Exact APIs vary slightly across EF Core versions and providers; if your team has a standard pattern, align with it. If uncertain, this needs to be clarified by the engineering team.
Best practices for EF Core and Dapper in production
Operational habits that prevent 80% of incidents.
How do you keep EF Core predictable under load
Treat EF Core as a powerful tool that needs guardrails.
- Prefer projections (SELECT) for read endpoints instead of returning full entities.
- Use no-tracking for read-only queries. (Microsoft Learn)
- Avoid accidental lazy loading in request paths (N+1 risk). (Microsoft Learn)
- Log SQL, timings, and parameter values with appropriate redaction.
How do you keep Dapper safe and maintainable
Most Dapper problems are not performance—they’re governance.
- Always parameterize (never string-concatenate user input).
- Store SQL in named queries (constants/files) with code review.
- Prefer explicit column lists (avoid SELECT *).
- Write integration tests for complex queries (schema drift happens).
What should you monitor regardless of ORM choice
Observability beats ideology.
- Query duration percentiles (P50/P95/P99)
- DB CPU, locks, deadlocks, and slow query logs
- Connection pool saturation
- Error rates and timeouts at the API layer
Common mistakes developers make with EF Core and Dapper
These are predictable failure modes you can prevent with code review:
- Using EF Core tracking for read endpoints by default: Tracking has purpose, but read-only queries are generally faster than no-tracking. (Microsoft Learn)
- Allowing lazy loading in high-traffic code paths: Lazy loading can cause extra round trips (N+1). (Microsoft Learn)
- Blaming the ORM for slow queries without checking the DB plan: Missing indexes and bad query shapes dominate most performance issues.
- Overusing Dapper for complex write workflows: You’ll rebuild unit of work, concurrency handling, and relationship integrity manually.
- Letting SQL sprawl: Without conventions, Dapper codebases can become string soup.
Summary: EF Core vs. Dapper
- EF Core is a full ORM that improves productivity with LINQ, tracking, migrations, and providers. (Microsoft Learn)
- Dapper is a simple object mapper that keeps SQL explicit and overhead low. (GitHub)
- Performance differences matter most on hot read paths, large result sets, or when tracking or lazy loading is misused. (Microsoft Learn)
- A hybrid approach is often the most practical option in real systems.
Add fast, reliable eSignatures to your .NET apps with BoldSign’s developer-friendly API—start integrating BoldSign’s API today
Related blogs
- Add eSignatures to Your Insurance Claim App with .NET
- Exploring the Latest Updates to Identity API Endpoints in .NET 8
- End-to-End eSignature Workflows in .NET with BoldSign SDK: Webinar
Note: This blog was originally published at boldsign.com
Top comments (0)