Dapper is a micro-ORM that maps query results to objects, while EF Core is a full-featured ORM that allows you to work with your database using LINQ and provides change tracking and migration tools.
Let’s see Dapper in action with a simple query. Imagine you have a Products table and you want to fetch all products with a price greater than 50.
using Dapper;
using System.Data.SqlClient;
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
public class ProductRepository
{
private readonly string _connectionString = "Server=myServer;Database=myDatabase;User Id=myUser;Password=myPassword;";
public IEnumerable<Product> GetExpensiveProducts()
{
using (var connection = new SqlConnection(_connectionString))
{
var sql = "SELECT ProductId, Name, Price FROM Products WHERE Price > @PriceThreshold";
return connection.Query<Product>(sql, new { PriceThreshold = 50 });
}
}
}
In this example, connection.Query<Product>(sql, new { PriceThreshold = 50 }) directly executes the SQL query and maps the results to Product objects. There’s no intermediate abstraction layer generating SQL for you. You write the SQL, and Dapper handles the object mapping.
Now, let’s look at EF Core doing something similar. With EF Core, you define your entities and a DbContext, and then you query using LINQ.
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
public class AppDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=myServer;Database=myDatabase;User Id=myUser;Password=myPassword;");
}
}
public class ProductRepositoryEF
{
public IEnumerable<Product> GetExpensiveProducts()
{
using (var context = new AppDbContext())
{
return context.Products.Where(p => p.Price > 50).ToList();
}
}
}
Here, context.Products.Where(p => p.Price > 50).ToList() translates the LINQ expression into SQL, executes it, and then materializes the results into Product objects. EF Core manages the mapping, change tracking, and database schema.
The fundamental difference lies in control and abstraction. Dapper gives you full control over your SQL. You write it, you tune it, and you know exactly what’s going to the database. This makes it incredibly fast and lightweight, as it has minimal overhead. It’s essentially a high-performance SQL executor with object mapping. EF Core, on the other hand, abstracts away the SQL. You work with .NET objects and LINQ, and EF Core figures out the database operations. This provides a higher level of abstraction, simplifying development for complex scenarios and offering features like automatic change tracking and migrations.
Dapper shines when performance is paramount and you want to write optimized SQL yourself, or when dealing with complex queries that are difficult to express in LINQ. It’s excellent for read-heavy applications or scenarios where you need fine-grained control over database interactions. EF Core is a great choice when you want to leverage LINQ for querying, benefit from automatic change tracking, and manage your database schema through migrations. It’s often preferred for new projects where developer productivity and a higher abstraction level are key.
When using Dapper, it’s common to see developers create extension methods for IDbConnection to encapsulate common query patterns. This allows you to write connection.QueryProductsByPrice(50) instead of repeating the SQL and mapping logic across your application, maintaining a cleaner codebase while still benefiting from Dapper’s performance and control.
The one thing that often surprises people about Dapper is how effortlessly it handles complex object graphs and relationships with its Query and QueryAsync methods when combined with the splitOn parameter. You can map a single SQL query that joins multiple tables into a hierarchy of objects without writing explicit mapping code for each relationship, making it powerful for fetching related data efficiently.
If you’re optimizing for raw speed and direct SQL control, Dapper is your go-to.