Skip to main content

Command Palette

Search for a command to run...

Entity Framework Core Setup Guide

Published
4 min read

Entity Framework Core (EF Core) is the standard open-source ORM for .NET applications. This guide covers the installation, configuration, and architectural patterns used in enterprise-grade production environments.

1. Prerequisites & Installation

EF Core is modular. You must install the specific provider for your database.

Required NuGet Packages

Run the following commands in your project directory or use the Visual Studio Package Manager.

1. Database Provider (Choose one)

  • SQL Server: Microsoft.EntityFrameworkCore.SqlServer

  • PostgreSQL: Npgsql.EntityFrameworkCore.PostgreSQL

  • SQLite: Microsoft.EntityFrameworkCore.Sqlite

  • MySQL: Pomelo.EntityFrameworkCore.MySql

2. Tooling (Required for Migrations)


2. The Standard "Code-First" Setup

This is the most common industry workflow: define C# classes first, then generate the database.

Step A: Define the Entity (The Model)

Entities should be "Plain Old CLR Objects" (POCOs). In clean architecture, these often live in a Domain or Core project.

// Models/Product.cs
public class Product
{
    public int Id { get; set; } // Auto-detected primary key
    public string Name { get; set; }
    public decimal Price { get; set; }

    // Foreign Key Relationship
    public int CategoryId { get; set; }
    public Category Category { get; set; }
}

Step B: Define the DbContext

The DbContext manages the connection and maps entities to tables.

// Data/AppDbContext.cs
using Microsoft.EntityFrameworkCore;

public class AppDbContext : DbContext
{
    // 1. The Constructor
    // Must accept DbContextOptions to allow external configuration (DI).
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
    {
    }

    // 2. The Tables
    public DbSet<Product> Products { get; set; }
    public DbSet<Category> Categories { get; set; }

    // 3. Fluent API Configuration (Optional but recommended for complex rules)
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Product>()
            .Property(p => p.Name)
            .IsRequired()
            .HasMaxLength(100);
    }
}

Step C: Configuration (Program.cs)

This is the Standard Industry Practice. Configuration happens in the application entry point using Dependency Injection (DI), not inside the Context class itself.

// Program.cs
var builder = WebApplication.CreateBuilder(args);

// Fetch connection string securely from appsettings.json or Key Vault
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");

// Register the Context with Scoped Lifetime (Default)
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(connectionString, sqlOptions => 
    {
        // Resiliency: Auto-retry on transient failure (network blip)
        sqlOptions.EnableRetryOnFailure(
            maxRetryCount: 5, 
            maxRetryDelay: TimeSpan.FromSeconds(10), 
            errorNumbersToAdd: null);
    }));

3. Alternative Configuration Patterns

Depending on your performance needs or architecture, you may choose one of these variations.

Variation A: DbContext Pooling (High Performance)

Used for high-throughput applications (2,000+ RPS) to reduce memory allocation overhead.

Configuration:

// Uses an Object Pool pattern instead of creating fresh objects
builder.Services.AddDbContextPool<AppDbContext>(options =>
    options.UseSqlServer(connectionString));

Note: Ensure your DbContext is stateless (stores no private data) to avoid leaking data between users.

Variation B: Design-Time Factory (CI/CD Support)

Standard setup requires the API to be runnable to create migrations. If your architecture separates the DbContext into a class library that cannot "run," you need a Factory. This is critical for CI/CD pipelines.

// Data/AppDbContextFactory.cs
public class AppDbContextFactory : IDesignTimeDbContextFactory<AppDbContext>
{
    public AppDbContext CreateDbContext(string[] args)
    {
        var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>();

        // Hardcoded or loaded from environment variables for migration generation only
        optionsBuilder.UseSqlServer("Server=...;Database=...");

        return new AppDbContext(optionsBuilder.Options);
    }
}

Variation C: Separate Assembly for Migrations

Keeps generated migration files out of your clean Application project.

builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(
        connectionString,
        x => x.MigrationsAssembly("MyProject.Infrastructure.Migrations") // Store migrations here
    ));

4. The "Database-First" Setup (Reverse Engineering)

Used when you have an existing legacy database and want to generate C# classes from it.

Command (Run in Terminal):

dotnet ef dbcontext scaffold "Server=...;Database=...;" Microsoft.EntityFrameworkCore.SqlServer --output-dir Models
  • --output-dir: Specifies where to put the generated classes.

  • --force: Overwrites existing files if the DB changes.


5. Executing Migrations

Once setup is complete, use these commands to sync code and database.

Action.NET CLI CommandVisual Studio (Package Manager)
Create Migrationdotnet ef migrations add NameOfChangeAdd-Migration NameOfChange
Apply to DBdotnet ef database updateUpdate-Database
Generate SQL Scriptdotnet ef migrations scriptScript-Migration
Remove Last Migrationdotnet ef migrations removeRemove-Migration

6. Best Practices Checklist

  1. Never Hardcode Credentials: Use appsettings.json (Development) and Azure Key Vault / Environment Variables (Production).

  2. Enable Retry Logic: Always use .EnableRetryOnFailure() for cloud databases (Azure SQL/AWS RDS) to handle temporary network drops.

  3. Split Configuration: Use IEntityTypeConfiguration<T> classes to separate your mapping logic from the AppDbContext file. This prevents the OnModelCreating method from becoming massive.

  4. No Logic in Context: The DbContext should strictly handle configuration. Business logic belongs in Services/Repositories.

  5. Use AsNoTracking: When reading data for "Read-Only" API endpoints, use .AsNoTracking() to improve performance by 2x-5x.

More from this blog

E

EF Core

31 posts