Introduction
In modern .NET applications, developers often face a common challenge — choosing between performance and productivity when working with databases. As we discussed earlier, Dapper is fast and efficient, while Entity Framework Core (EF Core) is easy to use and reduces development effort.
But what if you don’t want to choose just one?
This is where a hybrid data access approach comes in. It allows you to use both Dapper and EF Core together in the same application, taking advantage of the strengths of each.
In this article, we will understand how to design and implement a hybrid data access layer in simple terms, step by step.
What is a Hybrid Data Access Layer?
A hybrid data access layer means:
Using EF Core for standard operations like Create, Update, Delete
Using Dapper for performance-critical or complex queries
Instead of replacing one with the other, you combine both tools in a smart way.
Why Use a Hybrid Approach?
Let’s understand the reasoning in simple terms.
Benefits of EF Core
Benefits of Dapper
Combined Advantage
By using both:
When Should You Use Hybrid Approach?
You should consider this approach when:
Your application has both simple and complex queries
Performance matters in some parts of the system
You are building scalable APIs or microservices
You want flexibility in database operations
Architecture Overview
In a hybrid setup, your project is usually structured like this:
Controllers → Handle HTTP requests
Services → Business logic
Data Access Layer → EF Core + Dapper
You separate responsibilities clearly so that both tools can coexist without confusion.
Step-by-Step Implementation
Let’s go step by step to implement this in a .NET application.
Step 1: Install Required Packages
You need both EF Core and Dapper:
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Dapper
Step 2: Configure EF Core DbContext
Create your DbContext as usual:
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options)
{
}
public DbSet<User> Users { get; set; }
}
Register it in Program.cs:
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
Step 3: Create a Dapper Context
Dapper works with IDbConnection, so we create a helper class:
public class DapperContext
{
private readonly IConfiguration _configuration;
public DapperContext(IConfiguration configuration)
{
_configuration = configuration;
}
public IDbConnection CreateConnection()
=> new SqlConnection(_configuration.GetConnectionString("DefaultConnection"));
}
Register it:
builder.Services.AddSingleton<DapperContext>();
Step 4: Create Repository or Service Layer
Now we use both EF Core and Dapper in the same service.
public class UserService
{
private readonly AppDbContext _context;
private readonly DapperContext _dapper;
public UserService(AppDbContext context, DapperContext dapper)
{
_context = context;
_dapper = dapper;
}
// EF Core for simple operation
public async Task<User> GetUserById(int id)
{
return await _context.Users.FindAsync(id);
}
// Dapper for high-performance query
public async Task<IEnumerable<User>> GetActiveUsers()
{
using var connection = _dapper.CreateConnection();
var query = "SELECT * FROM Users WHERE IsActive = 1";
return await connection.QueryAsync<User>(query);
}
}
Step 5: Use in Controller
[ApiController]
[Route("api/users")]
public class UsersController : ControllerBase
{
private readonly UserService _service;
public UsersController(UserService service)
{
_service = service;
}
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
var user = await _service.GetUserById(id);
return Ok(user);
}
[HttpGet("active")]
public async Task<IActionResult> GetActive()
{
var users = await _service.GetActiveUsers();
return Ok(users);
}
}
Best Practices for Hybrid Approach
To avoid confusion and maintain clean architecture, follow these best practices:
Keep EF Core and Dapper usage clearly separated
Use EF Core for writes (Insert, Update, Delete)
Use Dapper for read-heavy queries
Avoid mixing both in a single method unnecessarily
Keep SQL queries organized (stored procedures or query files)
Common Mistakes to Avoid
Overusing Dapper everywhere (losing EF Core benefits)
Writing complex SQL when EF Core can handle it easily
Mixing responsibilities without clear structure
Not managing connections properly
Performance Consideration
Hybrid approach improves performance because:
This balance helps in building scalable applications without sacrificing developer productivity.
Summary
A hybrid data access layer in .NET combines the strengths of Entity Framework Core and Dapper by using EF Core for standard database operations and Dapper for performance-critical queries. This approach helps developers achieve both productivity and performance, making applications more efficient, scalable, and easier to maintain when implemented with proper structure and best practices.