Oğuzhan KARAGÜZEL

OĞUZHAN KARAGÜZEL

Software Developer

What Is EF Core? Setup and the Database First Approach

Author: Oğuzhan KARAGÜZEL ·

How you set up the data layer with Entity Framework Core (EF Core) is one of the first big decisions in a project: do you design the database first, or write the C# classes first? In this three-part series we compare the three approaches (Database First, Code First and Mixed) with hands-on examples. This first article explains what EF Core is, how to prepare your environment, and how to generate code from an existing database with the Database First approach.

What is EF Core?

EF Core is Microsoft's open-source, cross-platform ORM (Object-Relational Mapper). Instead of writing raw SQL against tables, you work with C# classes and write queries in LINQ. Its main features are:

  • Cross-platform: runs on Windows, Linux and macOS with .NET.
  • Performance: EF Core is noticeably faster than Entity Framework 6 and keeps improving with each release.
  • Easy integration: it fits naturally with ASP.NET Core, dependency injection and the rest of the .NET stack.
  • Rich feature set: LINQ queries, automatic change tracking, relational data support and a migration system.

EF Core supports many database providers. This series uses SQL Server; the examples work with other providers with minor changes.

The three approaches at a glance

Database First is for when the database already exists: EF Core reads it and generates the C# classes. Code First means writing the classes first and generating the database with migrations. Mixed combines both: you pull an existing database into code and then manage changes with migrations. Code First and Mixed are covered in the next articles of the series.

Setup: SQL Server, DBeaver and a console app

To try every example in the series on the same foundation, let's prepare the environment first. You need a SQL Server instance, a database client and Visual Studio. I use a Docker container for SQL Server and DBeaver to run SQL and inspect the database; you can use your own SQL Server installation instead and get the same result.

1. SQL Server with Docker

With Docker Desktop installed, start a SQL Server container with the command below. The password must be at least 8 characters and include upper-case and lower-case letters, digits and symbols. The user name is sa (system administrator).

Docker
docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=<YourStrongPassword>" \
  -p 5555:1433 --name sqlserver-efcore \
  -d mcr.microsoft.com/mssql/server:2022-latest

The container is ready within seconds. In the examples we connect to localhost,5555.

2. Connecting with DBeaver

In DBeaver choose Database → New Database Connection, pick SQL Server and click Next. Enter localhost as the host, 5555 as the port, sa as the user and the password you set, then click Finish. You can now write SQL, create tables and browse the database.

3. Console app and NuGet packages

Create a new Console App in Visual Studio (target .NET 8 or later) and install four NuGet packages:

.NET CLI
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package Microsoft.EntityFrameworkCore.Design

The Tools package provides the Package Manager Console commands (Scaffold-DbContext, Add-Migration and so on) and Design provides design-time services. Once they are installed, we are ready.

What is Database First?

With Database First you create the database structure first, and EF Core then generates the matching entity classes and the DbContext automatically. It is the natural choice when you must work with an existing database or when a separate team owns the database.

Sample database: okul (school)

Let's build a small database that contains all three relationship types: teacher–class (one-to-one), class–student (one-to-many) and student–club (many-to-many). Run the script below in DBeaver:

SQL
CREATE DATABASE okul;
GO
USE okul;
GO

CREATE TABLE Ogretmen (            -- Teacher
    OgretmenID INT PRIMARY KEY IDENTITY(1,1),
    Ad NVARCHAR(50) NOT NULL,
    Soyad NVARCHAR(50) NOT NULL,
    DogumTarihi DATE,
    Brans NVARCHAR(50)
);

CREATE TABLE Sinif (               -- Class
    SinifID INT PRIMARY KEY IDENTITY(1,1),
    SinifAdi NVARCHAR(50) NOT NULL,
    OgretmenID INT UNIQUE,         -- UNIQUE makes it one-to-one
    FOREIGN KEY (OgretmenID) REFERENCES Ogretmen(OgretmenID)
);

CREATE TABLE Ogrenci (             -- Student
    OgrenciID INT PRIMARY KEY IDENTITY(1,1),
    Ad NVARCHAR(50) NOT NULL,
    Soyad NVARCHAR(50) NOT NULL,
    DogumTarihi DATE,
    SinifID INT,                   -- one-to-many
    FOREIGN KEY (SinifID) REFERENCES Sinif(SinifID)
);

CREATE TABLE OgrenciKlupleri (     -- Clubs
    KlupID INT PRIMARY KEY IDENTITY(1,1),
    KlupAdi NVARCHAR(50) NOT NULL
);

-- Join table for the many-to-many relationship
CREATE TABLE OgrenciKlup (
    OgrenciID INT,
    KlupID INT,
    PRIMARY KEY (OgrenciID, KlupID),
    FOREIGN KEY (OgrenciID) REFERENCES Ogrenci(OgrenciID),
    FOREIGN KEY (KlupID) REFERENCES OgrenciKlupleri(KlupID)
);
SQL
-- Teachers first, then classes (so OgretmenID is populated)
INSERT INTO Ogretmen (Ad, Soyad, DogumTarihi, Brans) VALUES
('Ali', 'Yilmaz', '1975-05-15', 'Matematik'),
('Ayse', 'Demir', '1980-08-22', 'Fizik');

INSERT INTO Sinif (SinifAdi, OgretmenID) VALUES
('10A', 1),
('10B', 2);

INSERT INTO Ogrenci (Ad, Soyad, DogumTarihi, SinifID) VALUES
('Ahmet', 'Kaya', '2005-01-12', 1),
('Mehmet', 'Celik', '2004-03-17', 2);

INSERT INTO OgrenciKlupleri (KlupAdi) VALUES
('Science Club'),
('Sports Club');

INSERT INTO OgrenciKlup (OgrenciID, KlupID) VALUES
(1, 1), (1, 2), (2, 1);

Note: we insert the classes after the teachers, so every class has an OgretmenID value and the one-to-one relationship can actually be tested.

Generating code with Scaffold-DbContext

To generate C# code from an existing database, use the Scaffold-DbContext command. In Visual Studio open Tools → NuGet Package Manager → Package Manager Console and run the command below, adjusted to your connection details:

Package Manager Console
Scaffold-DbContext "Server=localhost,5555;Database=okul;User Id=sa;Password=<PASSWORD>;TrustServerCertificate=True" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Entities -ContextDir Context -Context OkulContext

Let's break the command down:

  • Scaffold-DbContext: reverse-engineers the existing database into C# classes.
  • Connection string: server, database name and credentials. Change it to match your environment.
  • Microsoft.EntityFrameworkCore.SqlServer: the NuGet package name of the database provider.
  • -OutputDir: the folder where the entity classes are written.
  • -ContextDir: the folder for the DbContext class. Without folder names, all classes land in the project root, so I recommend always setting them.

When the command runs, EF Core inspects the database and adds entity classes, the relationship configuration and a DbContext to your project.

Useful extra parameters

  • -Force: overwrites previously generated files; needed when you re-scaffold after a database change.
  • -Tables: generates only the listed tables.
  • -NoPluralize: turns off singular/plural conversion (you will see why it can help below).
  • -NoOnConfiguring: keeps the connection string out of the generated DbContext.

If you do not use Visual Studio, the .NET CLI does the same job (first run dotnet tool install --global dotnet-ef):

.NET CLI
dotnet ef dbcontext scaffold "Server=localhost,5555;Database=okul;User Id=sa;Password=<PASSWORD>;TrustServerCertificate=True" Microsoft.EntityFrameworkCore.SqlServer --output-dir Entities --context-dir Context --context OkulContext --no-onconfiguring

Running the generated code

To check that everything works, let's print the teacher names to the console:

C#
using var context = new OkulContext();

List<Ogretman> teachers = context.Ogretmen.ToList();

foreach (Ogretman teacher in teachers)
    Console.WriteLine($"Teacher: {teacher.Ad} {teacher.Soyad}");

When the program runs, the two teachers in the database are listed. You can try the other CRUD operations (insert, update, delete) through the same DbContext.

One detail may catch your eye: the table is named Ogretmen but the generated class is Ogretman. This comes from EF Core's English-based pluralizer; with non-English table names such surprises can happen, and the name of the DbSet property in the context may stay different. To avoid the confusion, consider using English table and column names or the -NoPluralize option.

How are many-to-many relationships generated?

Since EF Core 5, a join table that consists only of two foreign keys (like our OgrenciKlup) is not generated as a separate entity. Instead you get collection properties (skip navigations) on both sides, so you work with the club collection directly on the student and the student collection on the club. If you add extra columns to the join table (for example a membership date), EF Core generates it as a normal entity.

Pros and cons of Database First

Advantages

  • Quickly turns an existing database structure into code.
  • Matches the database architecture exactly.
  • Easy to use even with complex database structures.

Disadvantages

  • Changes you make in code are not reflected in the database automatically.
  • Every database change requires scaffolding again; forgetting the folder parameters can make the project messy. If you edited generated classes by hand, those edits are lost on regeneration (partial classes help here).
  • Heavy dependence on the database structure reduces flexibility.
  • Tracking database changes is harder than in a Code First project with a migration history.

When should you choose Database First?

If database specialists work on your project or the database is handed to you ready-made, Database First is often the right choice. Writing entity classes by hand for an existing database is tedious and error-prone. If you will design the database from scratch yourself, Code First is usually more comfortable, because you define relationships in code instead of managing them in SQL.

In the next article we cover Code First and migration management.