Entity Framework Core
What EF Core Is (Interview One-Liner)
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."
9️⃣ 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();
📌 Interview answer:
"Fluent API is better for complex rules and separation of concerns."
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" });
📌 Interview tip:
"Seed data is versioned via migrations."
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
One-Paragraph Interview Answer
"EF Core is a modern ORM that allows us to work with relational data using strongly typed C# objects. It uses DbContext to track changes and translate LINQ queries into SQL. We typically use Code First with migrations to manage schema changes, Fluent API for configuration, async operations for scalability, and services to encapsulate data access logic. EF Core improves productivity while still allowing raw SQL or stored procedures when needed."
If You Remember ONLY 7 Things
- DbContext = database session
- DbSet = table
- LINQ → SQL
- Change tracking
- Include for relationships
- Migrations sync schema
- Async everywhere