Entity Framework Core, DbContext, Migrations & LINQ Masterclass

โšก C# 12 & .NET 8 ๐ŸŸข Chapter 37 of 35 ๐Ÿ“‚ Phase 13: Databases & Entity Framework Core ๐Ÿ“… 2026 Edition
๐Ÿ“Œ Covered in this chapter: ORM ยท EF Core Architecture ยท Entity Classes ยท DbContext ยท DbSet ยท Connection Strings ยท Migrations ยท CRUD ยท Eager Loading Include() ยท Repository Pattern ยท Async LINQ

Welcome to Phase 13 (Chapter 37): Entity Framework Core (EF Core) โ€” ORM, DbContext, Migrations & LINQ Queries Masterclass! EF Core is Microsoft's official Object-Relational Mapper (ORM) for .NET. It lets you work with a database using C# objects instead of writing raw SQL queries. EF Core handles schema creation via Migrations, generates SQL under the hood, provides change tracking, relationship navigation, and async LINQ-based queries.

1ORM Ante Enti? EF Core Architecture
EF Core Architecture: Your C# Code (Entity Classes, LINQ Queries) โ”‚ โ–ผ [EF Core Runtime] DbContext โ€” tracks changes & manages entity state โ”‚ โ–ผ [Database Provider] SQL Server / PostgreSQL / SQLite / MySQL โ”‚ โ–ผ Actual Database (tables, rows, indices)
Terminal โ€” Install EF Core Packages
# Install EF Core for SQL Server
dotnet add package Microsoft.EntityFrameworkCore.SqlServer

# Install EF Core for SQLite (lightweight, good for dev)
dotnet add package Microsoft.EntityFrameworkCore.Sqlite

# Install EF Core Design tools (for migrations)
dotnet add package Microsoft.EntityFrameworkCore.Design

# Install EF Core Tools globally
dotnet tool install --global dotnet-ef
2Entity Classes & DbContext Configuration
C# โ€” Entity Model & DbContext โ–ถ Run in Compiler
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;

// 1. Entity Class (maps to database table)
public class Product
{
    public int Id { get; set; }                             // PK (auto-detected by convention)

    [Required, MaxLength(100)]
    public string Name { get; set; } = "";

    [Required, Range(0.01, double.MaxValue)]
    public decimal Price { get; set; }

    public int Stock { get; set; }
    public string Category { get; set; } = "";
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;

    // Navigation property (one Product has many OrderItems)
    public ICollection<OrderItem> OrderItems { get; set; } = new List<OrderItem>();
}

// 2. DbContext โ€” the central connection hub to the database
public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

    public DbSet<Product> Products { get; set; }
    public DbSet<Order> Orders { get; set; }
    public DbSet<OrderItem> OrderItems { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Fluent API configuration (alternative to Data Annotations)
        modelBuilder.Entity<Product>()
            .Property(p => p.Price)
            .HasColumnType("decimal(10,2)");

        modelBuilder.Entity<Product>()
            .HasIndex(p => p.Category); // Add database index on Category
    }
}
3Migrations โ€” Database Schema Management

Migrations in EF Core are version-controlled snapshots of your database schema changes. Instead of writing ALTER TABLE SQL scripts manually, you define C# entity changes and let EF Core generate the SQL migration script automatically.

Terminal โ€” EF Core Migration Commands
# Step 1: Create first migration (generates C# migration class)
dotnet ef migrations add InitialCreate

# Step 2: Apply migration to database (executes SQL ALTER TABLE scripts)
dotnet ef database update

# After adding new entity property (e.g., adding ImageUrl to Product):
dotnet ef migrations add AddProductImageUrl
dotnet ef database update

# Rollback to previous migration
dotnet ef database update PreviousMigrationName

# Remove last unapplied migration
dotnet ef migrations remove
4CRUD Operations โ€” Reading, Adding, Updating & Deleting
C# โ€” EF Core CRUD with Async LINQ โ–ถ Run in Compiler
// READ โ€” Get all products (async LINQ query)
List<Product> products = await context.Products
    .Where(p => p.Stock > 0)
    .OrderBy(p => p.Name)
    .ToListAsync();

// READ โ€” Find by primary key (most efficient)
Product? product = await context.Products.FindAsync(1);

// ADD โ€” Create new entity
var newProduct = new Product { Name = "Tablet", Price = 35000m, Stock = 20, Category = "Electronics" };
context.Products.Add(newProduct);
await context.SaveChangesAsync(); // Executes INSERT SQL

// UPDATE โ€” Modify existing entity
var existing = await context.Products.FindAsync(1);
if (existing != null)
{
    existing.Price = 72000m;
    existing.Stock -= 1;
    await context.SaveChangesAsync(); // Executes UPDATE SQL
}

// DELETE โ€” Remove entity
var toDelete = await context.Products.FindAsync(5);
if (toDelete != null)
{
    context.Products.Remove(toDelete);
    await context.SaveChangesAsync(); // Executes DELETE SQL
}
5Eager Loading with Include() & Repository Pattern
C# โ€” Eager Loading & Repository โ–ถ Run in Compiler
// Eager Loading โ€” Load related entities in a single SQL query
var orders = await context.Orders
    .Include(o => o.Customer)          // JOIN Customers table
    .Include(o => o.Items)             // JOIN OrderItems table
        .ThenInclude(i => i.Product)   // JOIN Products table
    .ToListAsync();

// Generic Repository Pattern
public interface IRepository<T> where T : class
{
    Task<List<T>> GetAllAsync();
    Task<T?> GetByIdAsync(int id);
    Task AddAsync(T entity);
    Task UpdateAsync(T entity);
    Task DeleteAsync(int id);
}

public class ProductRepository : IRepository<Product>
{
    private readonly AppDbContext _context;
    public ProductRepository(AppDbContext context) { _context = context; }

    public async Task<List<Product>> GetAllAsync() => await _context.Products.ToListAsync();
    public async Task<Product?> GetByIdAsync(int id) => await _context.Products.FindAsync(id);
    public async Task AddAsync(Product entity) { _context.Products.Add(entity); await _context.SaveChangesAsync(); }
    public async Task UpdateAsync(Product entity) { _context.Products.Update(entity); await _context.SaveChangesAsync(); }
    public async Task DeleteAsync(int id) { var p = await _context.Products.FindAsync(id); if (p != null) { _context.Products.Remove(p); await _context.SaveChangesAsync(); } }
}
6Technical FAQs

Q1: What is the difference between Eager Loading and Lazy Loading?

Eager Loading uses Include() to fetch related entities in ONE database query (JOIN). Lazy Loading fetches related entities ON DEMAND when first accessed, potentially causing the N+1 problem (one query per entity).

Q2: What does SaveChangesAsync() do internally?

EF Core's change tracker monitors all entity state changes (Added, Modified, Deleted). SaveChangesAsync() compares current vs original values and generates the minimum SQL (INSERT/UPDATE/DELETE) to sync changes to the database โ€” all wrapped in a transaction.