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:
- You define entities (C# classes)
DbContexttracks them- You query using LINQ
- EF Core translates LINQ → SQL
- Database executes SQL
- 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:
| State | Meaning |
|---|---|
| Added | Will INSERT |
| Modified | Will UPDATE |
| Deleted | Will DELETE |
| Unchanged | No 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);
Navigation Properties
public Country Country { get; set; }
Loading Related Data
❌ 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 First | Database First |
|---|---|
| Start from C# | Start from DB |
| Migrations | Scaffold |
| Flexible | Legacy 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()→ applyDown()→ 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:
ToListAsyncSaveChangesAsync
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
- DbContext = database session
- DbSet = table
- LINQ → SQL
- Change tracking
- Include for relationships
- Migrations sync schema
- Async everywhere