Skip to main content

Entity Framework Core

What EF Core Is

EF Core is a lightweight ORM that lets you work with databases using C# objects instead of raw SQL, while still allowing full control when needed.

ORM = Object ↔ Relational Mapper


How EF Core Works (Big Picture)

Think in 3 layers:

C# Entities

EF Core Mapping

Database Tables

Core Flow:

  1. You define entities (C# classes)
  2. DbContext tracks them
  3. You query using LINQ
  4. EF Core translates LINQ → SQL
  5. Database executes SQL
  6. Results → C# objects

Core Building Blocks (Must Memorize)

🔹 Entity

  • A C# class
  • Represents one database table
  • Properties = columns
public class Person
{
public Guid PersonID { get; set; }
public string PersonName { get; set; }
}

🔹 DbContext (VERY IMPORTANT)

DbContext = a session with the database

Responsibilities:

  • Database connection
  • Change tracking
  • LINQ → SQL translation
  • Saving changes
public class PersonsDbContext : DbContext
{
public DbSet<Person> Persons { get; set; }
}

📌 Lifetime: Scoped (one per request)


🔹 DbSet<T>

DbSet = a table

_db.Persons.Add(person);
_db.Persons.Where((p) => p.Name == "John");

EF Core Query Lifecycle (Interview Gold ⭐)

LINQ → Expression Tree → SQL → Database → Objects
  • Queries are not executed immediately
  • Executed only when:
    • ToList()
    • FirstOrDefault()
    • Single()

This is called deferred execution.


Change Tracking (Why EF Core Feels "Magic")

EF Core tracks entity states:

StateMeaning
AddedWill INSERT
ModifiedWill UPDATE
DeletedWill DELETE
UnchangedNo action
_db.Persons.Add(person);
_db.SaveChanges(); // generates SQL

📌 Interview line:

"EF Core detects changes automatically and generates the correct SQL."


CRUD with EF Core (Service Layer)

Create

_db.Persons.Add(person);
await _db.SaveChangesAsync();

Read

await _db.Persons.ToListAsync();

Update

person.Name = "New";
await _db.SaveChangesAsync();

Delete

_db.Persons.Remove(person);
await _db.SaveChangesAsync();

Relationships (VERY COMMON INTERVIEW TOPIC)

Relationship Types

  • One-to-One
  • One-to-Many ⭐
  • Many-to-Many

Example: Country → Persons (1 → Many)

entity
.HasOne((p) => p.Country)
.WithMany((c) => c.Persons)
.HasForeignKey((p) => p.CountryID);
public Country Country { get; set; }

❌ Lazy Loading (avoid)

  • Hidden queries
  • Performance issues

✅ Eager Loading

_db.Persons.Include((p) => p.Country);

📌 Interview rule:

"Only load what you need."


Configuration: Data Annotations vs Fluent API

Data Annotations

  • Simple
  • Close to model
  • Limited
[Required][MaxLength(50)];

Fluent API ⭐ (Preferred)

  • More powerful
  • Clean entities
  • Centralized
modelBuilder.Entity<Person>()
.Property(p => p.Name)
.HasMaxLength(50)
.IsRequired();

Code First vs Database First

Code FirstDatabase First
Start from C#Start from DB
MigrationsScaffold
FlexibleLegacy DB

📌 Most modern apps → Code First


Migrations (SUPER IMPORTANT)

Migrations keep your database schema in sync with your code

Common Commands

Add-Migration AddPersonsTable
Update-Database

Each migration:

  • Up() → apply
  • Down() → rollback

📌 Best practice:

  • Small migrations
  • Clear names
  • Review generated SQL

Seed Data

Used for:

  • Lookup tables
  • Demo data
  • Dev/test environments
modelBuilder.Entity<Country>()
.HasData(new Country { CountryID = ..., CountryName = "Japan" });

Connection Strings

Stored in:

  • appsettings.json
  • Environment variables
  • User secrets
options.UseSqlServer(
builder.Configuration.GetConnectionString("DefaultConnection"),
);

📌 Production → environment variables / Key Vault


Async EF Core (NON-NEGOTIABLE)

Always use:

  • ToListAsync
  • SaveChangesAsync

Why?

  • Scalability
  • Non-blocking threads

📌 Interview line:

"Async EF prevents thread starvation under load."


Stored Procedures (When Needed)

EF Core supports them:

_db.Persons.FromSqlRaw("EXEC GetAllPersons");

Use when:

  • Legacy DB
  • Complex SQL
  • Performance-critical paths

Best Practices (Memorize This List)

✅ Use services (not controllers) ✅ Use async everywhere ✅ Use DTOs ✅ Avoid lazy loading ✅ Use Include carefully ✅ Prefer Fluent API ✅ Small migrations ❌ Don't expose DbContext everywhere


If You Remember ONLY 7 Things

  1. DbContext = database session
  2. DbSet = table
  3. LINQ → SQL
  4. Change tracking
  5. Include for relationships
  6. Migrations sync schema
  7. Async everywhere