Code-First Approach
In a new project like ShopSmart, the C# model can be the source of truth: you design classes, configure them, and EF Core creates the database to match. This lesson shows the Fluent API and, most usefully, the exact CREATE TABLE script EF Core produces.
On this page · 6 min · Intermediate
On this page
- In simple words
- Learning objectives
- Explanation
- Syntax
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 6 min
- Difficulty
- Intermediate
- Updated
- Oct 29, 2026
Related tutorials
In simple words#
Code-first is designing the room from the furniture plan: write C# classes first and let EF Core create the tables.
- Entity classes
- C# classes that become tables.
- Conventions
- Id becomes the primary key automatically.
- Migrations
- Turn class changes into schema changes.
Learning objectives#
- Design entities and relationships in C#
- Configure them with the Fluent API in IEntityTypeConfiguration classes
- Add unique indexes, lengths, precision and delete behaviour
- Inspect the generated database schema
Explanation#
Start from classes: Product, Category, Order. Conventions give you keys and relationships; the Fluent API (in OnModelCreating) adds everything else.
Keep configuration tidy with one IEntityTypeConfiguration<T> class per entity and modelBuilder.ApplyConfigurationsFromAssembly(...) in the context.
Common settings: HasMaxLength, IsRequired, HasPrecision(10, 2), HasIndex(p => p.Sku).IsUnique(), HasOne/WithMany/HasForeignKey, OnDelete(DeleteBehavior.Restrict), HasDefaultValueSql("SYSUTCDATETIME()").
Fluent API wins over attributes and keeps entities free of persistence details — handy when the same classes are shared with other layers.
Create the database with migrations (next lesson). For a quick look at the schema, db.Database.GenerateCreateScript() prints the SQL EF Core would run.
Syntax#
public class ProductConfiguration : IEntityTypeConfiguration<Product>
{
public void Configure(EntityTypeBuilder<Product> b)
{
b.Property(p => p.Name).HasMaxLength(100).IsRequired();
b.Property(p => p.Price).HasPrecision(10, 2);
b.HasIndex(p => p.Sku).IsUnique();
}
}
Code example: Model, configuration and the generated schema#
// Program.cs
using Microsoft.EntityFrameworkCore;
var options = new DbContextOptionsBuilder<ShopDbContext>()
.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=ShopSmart_CodeFirst;Trusted_Connection=True;TrustServerCertificate=True")
.Options;
await using var db = new ShopDbContext(options);
var script = db.Database.GenerateCreateScript(); // the schema EF Core derives from the model
Console.WriteLine(string.Join('\n', script.Split('\n').Where(line => line.Trim() is not ("" or "GO"))));
// Models/Catalog.cs
public class Category
{
public int Id { get; set; }
public string Name { get; set; } = "";
public List<Product> Products { get; set; } = [];
}
public class Product
{
public int Id { get; set; }
public string Sku { get; set; } = "";
public string Name { get; set; } = "";
public decimal Price { get; set; }
public DateTime CreatedAt { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; } = null!;
}
// Data/ShopDbContext.cs
using Microsoft.EntityFrameworkCore;
public class ShopDbContext(DbContextOptions<ShopDbContext> options) : DbContext(options)
{
public DbSet<Product> Products => Set<Product>();
public DbSet<Category> Categories => Set<Category>();
protected override void OnModelCreating(ModelBuilder modelBuilder) =>
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ShopDbContext).Assembly);
}
// Data/Configurations/ProductConfiguration.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
public class ProductConfiguration : IEntityTypeConfiguration<Product>
{
public void Configure(EntityTypeBuilder<Product> product)
{
product.Property(p => p.Sku).HasMaxLength(20).IsRequired();
product.HasIndex(p => p.Sku).IsUnique();
product.Property(p => p.Name).HasMaxLength(100).IsRequired();
product.Property(p => p.Price).HasPrecision(10, 2);
product.Property(p => p.CreatedAt).HasDefaultValueSql("SYSUTCDATETIME()");
product.HasOne(p => p.Category)
.WithMany(c => c.Products)
.HasForeignKey(p => p.CategoryId)
.OnDelete(DeleteBehavior.Restrict); // a category with products cannot be deleted
}
}
public class CategoryConfiguration : IEntityTypeConfiguration<Category>
{
public void Configure(EntityTypeBuilder<Category> category)
{
category.Property(c => c.Name).HasMaxLength(50).IsRequired();
category.HasIndex(c => c.Name).IsUnique();
}
}
CREATE TABLE [Categories] (
[Id] int NOT NULL IDENTITY,
[Name] nvarchar(50) NOT NULL,
CONSTRAINT [PK_Categories] PRIMARY KEY ([Id])
);
CREATE TABLE [Products] (
[Id] int NOT NULL IDENTITY,
[Sku] nvarchar(20) NOT NULL,
[Name] nvarchar(100) NOT NULL,
[Price] decimal(10,2) NOT NULL,
[CreatedAt] datetime2 NOT NULL DEFAULT (SYSUTCDATETIME()),
[CategoryId] int NOT NULL,
CONSTRAINT [PK_Products] PRIMARY KEY ([Id]),
CONSTRAINT [FK_Products_Categories_CategoryId] FOREIGN KEY ([CategoryId]) REFERENCES [Categories] ([Id]) ON DELETE NO ACTION
);
CREATE UNIQUE INDEX [IX_Categories_Name] ON [Categories] ([Name]);
CREATE INDEX [IX_Products_CategoryId] ON [Products] ([CategoryId]);
CREATE UNIQUE INDEX [IX_Products_Sku] ON [Products] ([Sku]);This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.
Step-by-step explanation#
- The entity classes contain no attributes — all mapping lives in configuration classes.
- ApplyConfigurationsFromAssembly finds ProductConfiguration and CategoryConfiguration automatically.
- GenerateCreateScript shows the result: nvarchar lengths, decimal(10,2), a default constraint, the foreign key with NO ACTION (Restrict) and unique indexes.
- No database is created here — the script is generated from the model alone.
- In the next lesson, migrations turn this model into real tables and keep them in sync as the model changes.
Real-world use case#
ShopSmart is code-first: developers change entities and configuration classes, generate a migration, review the SQL in the pull request, and the pipeline applies it to staging and production.
Common mistakes#
The default for required relationships is Cascade: deleting a category would delete its products. Choose Restrict where data must not disappear.
Hundreds of lines in one method are hard to review. Use IEntityTypeConfiguration classes.
Checking “SKU already exists” in code is not enough under concurrency. Add a unique index.
Best practices#
- One configuration class per entity
- Explicit lengths and precision
- Restrict deletes for important data
- Review generated SQL before applying
Remember this#
Code-first: C# model → migrations → database.
IEntityTypeConfiguration<T> + ApplyConfigurationsFromAssembly.
GenerateCreateScript() shows the schema.
Practice exercise#
Your task: Add an Order entity with Id, OrderNumber (unique, max 20 chars), Total (precision 12,2) and PlacedAt (default SYSUTCDATETIME()), configured in an OrderConfiguration class.
Need a hint?
Show solution
// Models/Order.cs
public class Order
{
public int Id { get; set; }
public string OrderNumber { get; set; } = "";
public decimal Total { get; set; }
public DateTime PlacedAt { get; set; }
}
// Data/Configurations/OrderConfiguration.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
public class OrderConfiguration : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> order)
{
order.Property(o => o.OrderNumber).HasMaxLength(20).IsRequired();
order.HasIndex(o => o.OrderNumber).IsUnique();
order.Property(o => o.Total).HasPrecision(12, 2);
order.Property(o => o.PlacedAt).HasDefaultValueSql("SYSUTCDATETIME()");
}
}
// Data/ShopDbContext.cs
using Microsoft.EntityFrameworkCore;
public class ShopDbContext(DbContextOptions<ShopDbContext> options) : DbContext(options)
{
public DbSet<Order> Orders => Set<Order>();
protected override void OnModelCreating(ModelBuilder modelBuilder) =>
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ShopDbContext).Assembly);
}
Mini quiz#
What is the source of truth in code-first?
Want more? Practise all ASP.NET Core MVC MCQs · ASP.NET Core MVC interview questions
My notes#
Saved in this browser.