Dockerizing Your Web API with SQL Server, Dapper, and FluentMigrator
Source: Dev.to
Introduction
In Parts 1 and 2 we covered FluentMigrator for schema management and CI/CD automation. In this part we’ll dockerize the entire stack—SQL Server, migrations, and the Dapper‑powered Web API—so you can spin up a complete development environment with a single command.
The Complete Stack
| Component | Technology | Purpose |
|---|---|---|
| Database | SQL Server 2022 | Data storage |
| Migrations | FluentMigrator | Schema versioning |
| Data Access | Dapper | High‑performance SQL mapping |
| API | ASP.NET Core 8 | HTTP endpoints |
| Orchestration | Docker Compose | Container management |
What We’re Building
┌─────────────────────────────────────────────────────────────┐
│ docker compose up │
└─────────────────────────────────────────────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ sqlserver│ │db-migrate│ │product‑api│
│ :1433 │ │ (runs │ │ :5050 │
│ │◄──│ once) │──►│ │
└──────────┘ └──────────┘ └──────────┘
│ │ │
└───────────────┴───────────────┘
│
productapi‑network
The flow
- sqlserver starts and waits until healthy.
- db‑migrate runs FluentMigrator migrations (once), then exits.
- product‑api starts only after migrations succeed.
Why Dockerize?
| Benefit | Description |
|---|---|
| One‑command setup | docker compose up – done |
| Consistent environments | Same on your machine, teammates’, CI/CD |
| No local installs | No SQL Server, no SDK required |
| Isolated | Won’t conflict with other projects |
| Production‑like | Test exactly what you’ll deploy |
Project Structure
ProductApi/
├── docker-compose.yml # Orchestrates everything
├── src/
│ ├── ProductWebAPI/
│ │ ├── Dockerfile # API container
│ │ ├── Controllers/
│ │ └── Services/
│ ├── ProductWebAPI.Database/
│ │ ├── Dockerfile # Migration runner
│ │ └── Migrations/
│ └── CommonComps/
│ ├── Models/ # Domain models
│ └── Repositories/ # Dapper implementations
Quick Recap: Dapper in This Stack
┌─────────────────────────────────────────────────────────────┐
│ ProductsController │
│ (HTTP endpoints) │
└─────────────────────────┬───────────────────────────────────┘
│
┌─────────────────────────▼───────────────────────────────────┐
│ ProductService │
│ (Business logic) │
└─────────────────────────┬───────────────────────────────────┘
│
┌─────────────────────────▼───────────────────────────────────┐
│ ProductRepository (Dapper) │
│ Write SQL → Get C# objects back │
└─────────────────────────┬───────────────────────────────────┘
│
┌─────────────────────────▼───────────────────────────────────┐
│ SQL Server (FluentMigrator‑managed) │
└─────────────────────────────────────────────────────────────┘
Example Repository
public class ProductRepository : IProductRepository
{
private readonly string _connectionString;
public ProductRepository(IConfiguration configuration)
{
_connectionString = configuration.GetConnectionString("DefaultConnection")
?? throw new ArgumentNullException("Connection string not found");
}
private IDbConnection CreateConnection() => new SqlConnection(_connectionString);
public async Task GetByIdAsync(int id)
{
const string sql = @"
SELECT p.*, c.*
FROM Products p
INNER JOIN Categories c ON p.CategoryId = c.Id
WHERE p.Id = @Id";
using var connection = CreateConnection();
var result = await connection.QueryAsync(
sql,
(product, category) => { product.Category = category; return product; },
new { Id = id },
splitOn: "Id"
);
return result.FirstOrDefault();
}
public async Task CreateAsync(Product product)
{
const string sql = @"
INSERT INTO Products (Name, SKU, Description, Price, CategoryId, IsActive, CreatedAt)
OUTPUT INSERTED.Id
VALUES (@Name, @SKU, @Description, @Price, @CategoryId, @IsActive, GETUTCDATE())";
using var connection = CreateConnection();
return await connection.ExecuteScalarAsync(sql, product);
}
}
Key Dapper Patterns
QueryAsync– Returns a collection.QueryFirstOrDefaultAsync– Returns a single item ornull.ExecuteScalarAsync– Returns a single value (e.g., inserted ID).ExecuteAsync– Returns the number of rows affected.
Why Dapper?
- ⚡ Near raw ADO.NET performance.
- 💪 Full control over SQL.
- 📦 Lightweight (no heavy ORM overhead).
Step 1: Dockerfile for the Web API
File: src/ProductWebAPI/Dockerfile
# Use the official .NET 8 SDK image for building
FROM mcr.microsoft.com/dotnet