Entity Framework Core Fundamentals
Level: Intermediate
EF Core is an ORM (Object-Relational Mapper). Learn this after understanding databases and basic SQL. EF Core generates SQL automatically, reducing code but hiding details. Great for rapid development but requires understanding lazy loading, N+1 queries, and change tracking.
- Entity Framework Core: ORM that maps database tables to C# objects (higher abstraction than ADO.NET)
- DbContext: Bridge between code and database (Contains DbSet properties for each entity)
- Entity models: C# classes that map to database tables (properties = columns, relationships = foreign keys)
- Code-First approach: Define C# models first, EF generates database schema from models (via migrations)
- Database-First approach: Create database first, EF scaffolds models from existing schema
- Migrations:
dotnet ef migrations add CreateStudentTablegenerates SQL,dotnet ef database updateapplies it - Migration workflow: Model change →
migrations add→ review generated SQL →database update→ database changes - LINQ to Entities:
_context.Students.Where(s => s.ClassName == "10A")translates to SQL automatically .Include()eager loading:_context.Students.Include(s => s.Exams)prevents N+1 queries (load related data upfront).ThenInclude(): Nested includes (Include(s => s.Exams).ThenInclude(e => e.Subjects)) load multiple relationship levels- One-to-Many: Student has many Exams (foreign key ExamId in Exam table points to Student)
- Many-to-Many: Student and Subject through StudentSubject junction table with composite key
- Navigation properties:
student.Examsaccesses related objects (lazy or eager loaded depending on configuration) - Change tracking: EF automatically tracks entity modifications (
student.Name = "New"; SaveChanges()generates UPDATE) - SaveChanges(): Commits all pending changes to database (INSERT, UPDATE, DELETE in single transaction)
- SaveChangesAsync(): Async version of SaveChanges (preferred in modern applications)
- Lazy loading: Access related data only when accessed (danger: N+1 queries, performance issue)
- N+1 query problem: 1 main query + N queries for related data (fix: use .Include() to eager load)
- Query filtering:
.Where(),.Select(),.OrderBy()filter/project results (executes on database, not in memory) .AsNoTracking(): Query without change tracking (read-only scenarios, better performance for large queries)- Batch operations: Update multiple rows with
.ExecuteUpdate(), delete with.ExecuteDelete()(EF Core 7+) - Transactions:
using (var trans = _context.Database.BeginTransaction())for multi-operation consistency - Connection string configuration: In OnConfiguring or dependency injection (.UseSqlServer in Startup)
- School Management entities: Student, Teacher, Subject, Exam, ExamResult, Fee, FeePayment, Attendance, AuditLog
- Common mistakes: Missing .Include() (N+1 queries), lazy loading in loops, forgetting SaveChanges(), tracking deleted entities
- When to use EF Core: Rapid development, complex relationships, database migrations, small-medium datasets
What is EF Core?
ERM = Entity Framework Core
Object-Relational Mapper. Maps database tables to C# classes. High-level abstraction over ADO.NET.
ADO.NET ← Raw SQL, parameters, SqlCommand
EF Core ← LINQ, automatic SQL generation
Dapper ← SQL-first, light mapping
DbContext and Entities
DbContext = bridge between code and database.
using Microsoft.EntityFrameworkCore;
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public string RollNumber { get; set; }
public string ClassName { get; set; }
public StudentStatus Status { get; set; }
}
public enum StudentStatus { Active, Inactive, Graduated }
public class SmsDbContext : DbContext
{
public DbSet<Student> Students { get; set; }
public DbSet<Exam> Exams { get; set; }
public DbSet<ExamResult> ExamResults { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseSqlServer("Server=localhost;Database=SmsDb;Trusted_Connection=true;");
}
}
Code-First Approach
Define classes first. EF creates database schema.
public class Student
{
[Key]
public int Id { get; set; }
[Required]
[StringLength(100)]
public string Name { get; set; }
[Required]
[StringLength(50)]
public string RollNumber { get; set; }
[Required]
[Range(1, 12)]
public int ClassNumber { get; set; }
[Required]
public StudentStatus Status { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
Migrations
Generate SQL from classes.
dotnet ef migrations add InitialCreate
dotnet ef database update
Creates: Students table with columns from properties.
Basic LINQ Queries
public class StudentService
{
private readonly SmsDbContext _context;
public StudentService(SmsDbContext context)
{
_context = context;
}
// Get all students
public async Task<List<Student>> GetStudentsAsync()
{
return await _context.Students.ToListAsync();
}
// Get by ID
public async Task<Student> GetStudentAsync(int id)
{
return await _context.Students.FirstOrDefaultAsync(s => s.Id == id);
}
// Filter
public async Task<List<Student>> GetStudentsByClassAsync(string className)
{
return await _context.Students
.Where(s => s.ClassName == className)
.ToListAsync();
}
// Count
public async Task<int> GetStudentCountAsync()
{
return await _context.Students.CountAsync();
}
// Order
public async Task<List<Student>> GetStudentsOrderedAsync()
{
return await _context.Students
.OrderBy(s => s.Name)
.ToListAsync();
}
}
Insert, Update, Delete
public async Task CreateStudentAsync(Student student)
{
_context.Students.Add(student);
await _context.SaveChangesAsync();
}
public async Task UpdateStudentAsync(int id, Student updatedStudent)
{
var student = await _context.Students.FindAsync(id);
if (student != null)
{
student.Name = updatedStudent.Name;
student.Status = updatedStudent.Status;
await _context.SaveChangesAsync();
}
}
public async Task DeleteStudentAsync(int id)
{
var student = await _context.Students.FindAsync(id);
if (student != null)
{
_context.Students.Remove(student);
await _context.SaveChangesAsync();
}
}
Change Tracking
EF tracks changes automatically.
var student = await _context.Students.FindAsync(1);
student.Name = "Updated Name";
// Don't need to call Update()
await _context.SaveChangesAsync();
EF detects property changes and generates SQL UPDATE automatically.
Relationships
One-to-Many: Student → Exams
public class Exam
{
public int Id { get; set; }
public string Name { get; set; }
public int SubjectId { get; set; }
// Navigation property
public Subject Subject { get; set; }
}
public class Subject
{
public int Id { get; set; }
public string Name { get; set; }
// Navigation collection
public List<Exam> Exams { get; set; } = new();
}
Many-to-Many: Student ↔ Subject (through StudentSubject)
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public List<StudentSubject> StudentSubjects { get; set; } = new();
}
public class Subject
{
public int Id { get; set; }
public string Name { get; set; }
public List<StudentSubject> StudentSubjects { get; set; } = new();
}
public class StudentSubject
{
public int StudentId { get; set; }
public Student Student { get; set; }
public int SubjectId { get; set; }
public Subject Subject { get; set; }
}
Key Takeaways
- EF Core = object mapping (classes ↔ tables)
- DbContext = database connection and query interface
- Code-First = define classes, migrations create schema
- LINQ = type-safe queries in C#
- Change tracking = automatic update detection
- Relationships = navigation properties
- async/await = non-blocking database operations
Use LINQ. Never build SQL strings manually.
Use ChatGPT, Claude, or Copilot to go deeper on Entity Framework Core Fundamentals. Try these prompts:
"What's the difference between EF Core and ADO.NET?""How do migrations work?""What is change tracking?""How do I define relationships?""Quiz me on EF Core"
💡 Tip: After reading this article, paste your own code into AI and ask "What could go wrong here and why?" — fastest way to find edge cases and deepen understanding.