If you’re reading this, chances are you’re either just starting with Entity Framework Core or you’re moving an older project to .NET 9 and want a quick but reliable setup guide. I’ve been in the same boat before, when we were starting a back-office system for handling merchant settlements, we needed a lightweight but powerful ORM that would scale as the product grows. EF Core was our go-to, and I’ve never regretted that decision (well, except during some migration issues but that’s a story for another post!).
Today, I’ll walk you through how I install and configure EF Core in .NET 9 projects. This guide is not just “hello world” level. I’ll also share some real mistakes I made so you can avoid them.
Prerequisites
Before we dive in, make sure you have:
- .NET 9 SDK (Preview or latest RC)
- A code editor (I use Visual Studio 2022)
- SQL Server 2022 (you can use LocalDB, Docker, or your favorite flavor)
- Basic knowledge of C#
Step 1: Creating a New Project
Let’s start by creating a new Web API project. You can also use Blazor or Console depending on your use case.
dotnet new webapi -n MyEfCoreApp
cd MyEfCoreApp
In my case, this is a service that exposes APIs for managing inventory and sales data. Most of the logic relies on DB access, so EF Core is critical here.
Step 2: Installing EF Core NuGet Packages
For SQL Server, we need a few essential packages.
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.Tools
Why all three?
- SqlServer: the main provider
- Design: required for migrations
- Tools: gives you CLI commands like dotnet ef
In our old project, we forgot to install Design, and we spent a whole hour wondering why dotnet ef migrations add kept failing.
Step 3: Creating the DbContext and Entity Models
At this point, we’ll define our data structure, basically telling EF Core what tables and columns we want in our database.
Let’s start with a simple entity Product.
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
This is what we call a POCO class or Plain Old CLR Object. It just means a simple C# class without any base classes or fancy dependencies.
- Id: This will be automatically treated as the primary key by EF Core. No need to decorate with attributes like [Key] unless you want to be explicit.
- Name: A required text field. We’ll make sure this has a max length later.
- Price: Standard decimal field, good for monetary values. EF Core maps this to decimal(18,2) by default in SQL Server.
In my inventory system, this was part of the product catalog. Each record in the table represented a unique SKU (Stock Keeping Unit).
Next, let’s create the DbContext.
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) {}
public DbSet<Product> Products { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>()
.Property(p => p.Name)
.IsRequired()
.HasMaxLength(100);
}
}
Think of DbContext as the middleman between your C# classes and your database.
- AppDbContext inherits from DbContext, this gives us all the power to query and save data.
- The constructor accepts DbContextOptions, this is where you inject your connection string later.
- DbSet acts like a virtual table. This line tells EF Core: “Hey, expect a Products table in the database, and it should match the Product class structure.”
Now, let’s talk about OnModelCreating.
This is where we configure the finer details of how the model maps to the database.
modelBuilder.Entity<Product>()
.Property(p => p.Name)
.IsRequired()
.HasMaxLength(100);
Here, we’re telling EF Core:
- Name must have a value — no nulls allowed (IsRequired()).
- Limit the length to 100 characters — this is to avoid storing too much junk data by accident.
I learned the importance of HasMaxLength() the hard way, one of our old tables didn’t have this, and people started typing long product descriptions into the Name field. Eventually, we had to trim them manually with a script.
Here’s a recap of why we do all this:
Element | Purpose |
---|---|
Product | Maps to a table (e.g. Products) |
DbContext | The brain of EF Core; handles queries, inserts, migrations |
DbSet | Think of this like SELECT * FROM Products |
OnModelCreating | Configure columns, relationships, constraints programmatically |
This setup is now ready to connect with a SQL Server database in the next step. You can now start building real-world features like product listing, search, and CRUD without writing raw SQL.
Step 4: Configure EF Core in Program.cs (or Startup.cs)
In .NET 9, most projects are using the minimal hosting model. Here’s how you wire up the DbContext:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
app.Run();
And don’t forget the appsettings.json:
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=MyEfCoreDb;Trusted_Connection=True;TrustServerCertificate=True;"
}
}
In one of my older projects, I forgot to add TrustServerCertificate=true; when using SSL on local SQL Server, took me an entire evening to figure out the trust issue.
Step 5: Run Your First Migration
Let’s generate the database schema.
dotnet ef migrations add InitialCreate
dotnet ef database update
This will:
- Create a Migrations folder with a snapshot and SQL
- Apply that schema to your SQL Server DB
Organizing Cleanly with a Real Project Setup
In one production project, we split the layers:
- MyApp.Domain: entities, enums
- MyApp.Infrastructure: DbContext, configurations
- MyApp.WebApi: controllers, service DI, config
Sample structure:
src/
│
├── MyApp.Domain/
│ └── Entities/Product.cs
│
├── MyApp.Infrastructure/
│ └── DbContext/AppDbContext.cs
│
└── MyApp.WebApi/
└── Program.cs
In MyApp.Infrastructure, you can also split EF configuration like this:
public class ProductConfiguration : IEntityTypeConfiguration<Product>
{
public void Configure(EntityTypeBuilder<Product> builder)
{
builder.Property(p => p.Name).IsRequired().HasMaxLength(100);
}
}
Then in OnModelCreating:
modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);
Makes your DbContext file less bloated.
Common Errors and My Lessons Learned
- “No provider has been configured”. Usually caused by missing UseSqlServer or not registering the DbContext.
- “No migrations found”. Happened to me when I ran dotnet ef in the wrong folder.
- Connection timeout. Check if your SQL Server is running, especially in Docker or Azure.
Final Checklist Before You Go
- Installed all required NuGet packages
- Created DbContext and models
- Configured connection string and DI
- Ran initial migration
- Verified table creation
References and Credits
- Official EF Core Docs: https://learn.microsoft.com/en-us/ef/core
- EF Core GitHub: https://github.com/dotnet/efcore
- Connection string help: https://www.connectionstrings.com/sql-server
- Inspiration from my past project.
I hope this post helps you get started with EF Core on .NET 9 easily. Everything I wrote here is based on actual experiences, good and bad ones. If you encounter issues along the way, don’t panic. Every error you get is part of the learning.