Learn EF Core: entity classes, DbContext, LINQ queries, migrations, and performance best practices for database access.
Entity Framework Core (EF Core) is the recommended ORM for .NET applications. It translates LINQ queries to SQL, manages database schema through migrations, and handles change tracking so you work with C# objects instead of raw SQL.
Define entity classes and configure a DbContext with DbSet<T> properties:
1// Entity classes
2public class Product
3{
4 public int Id { get; set; }
5 public string Name { get; set; } = "";
6 public decimal Price { get; set; }
7 public int CategoryId { get; set; }
8 public Category Category { get; set; } = null!;
9 public List<OrderItem> OrderItems { get; set; } = new();
10}
11
12public class Category
13{
14 public int Id { get; set; }
15 public string Name { get; set; } = "";
16 public List<Product> Products { get; set; } = new();
17}
18
19public class Order
20{
21 public int Id { get; set; }
22 public DateTime OrderDate { get; set; }
23 public string CustomerEmail { get; set; } = "";
24 public OrderStatus Status { get; set; }
25 public List<OrderItem> Items { get; set; } = new();
26 public decimal Total => Items.Sum(i => i.Quantity * i.UnitPrice);
27}
28
29public class OrderItem
30{
31 public int Id { get; set; }
32 public int OrderId { get; set; }
33 public int ProductId { get; set; }
34 public int Quantity { get; set; }
35 public decimal UnitPrice { get; set; }
36 public Order Order { get; set; } = null!;
37 public Product Product { get; set; } = null!;
38}
39
40public enum OrderStatus { Pending, Processing, Shipped, Delivered, Cancelled }EF Core translates LINQ method chains to optimized SQL:
1public class AppDbContext : DbContext
2{
3 public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
4
5 public DbSet<Product> Products => Set<Product>();
6 public DbSet<Category> Categories => Set<Category>();
7 public DbSet<Order> Orders => Set<Order>();
8 public DbSet<OrderItem> OrderItems => Set<OrderItem>();
9
10 protected override void OnModelCreating(ModelBuilder modelBuilder)
11 {
12 // Product configuration
13 modelBuilder.Entity<Product>(entity =>
14 {
15 entity.HasKey(e => e.Id);
16 entity.Property(e => e.Name).IsRequired().HasMaxLength(200);
17 entity.Property(e => e.Price).HasPrecision(18, 2);
18 entity.HasOne(e => e.Category)
19 .WithMany(c => c.Products)
20 .HasForeignKey(e => e.CategoryId);
21 });
22
23 // Order configuration
24 modelBuilder.Entity<Order>(entity =>
25 {
26 entity.HasKey(e => e.Id);
27 entity.Property(e => e.CustomerEmail).IsRequired().HasMaxLength(255);
28 entity.Property(e => e.Status).HasConversion<string>();
29 });
30
31 // Seed data
32 modelBuilder.Entity<Category>().HasData(
33 new Category { Id = 1, Name = "Electronics" },
34 new Category { Id = 2, Name = "Books" }
35 );
36 }
37}
38
39// Registration in Program.cs
40builder.Services.AddDbContext<AppDbContext>(options =>
41 options.UseNpgsql(builder.Configuration.GetConnectionString("Default")));Migrations track and apply incremental schema changes:
1// Basic queries
2var products = await _context.Products.ToListAsync();
3var product = await _context.Products.FindAsync(id);
4var filtered = await _context.Products.Where(p => p.Price > 100).ToListAsync();
5
6// Eager loading with Include
7var productsWithCategory = await _context.Products
8 .Include(p => p.Category)
9 .ToListAsync();
10
11var ordersWithItems = await _context.Orders
12 .Include(o => o.Items)
13 .ThenInclude(i => i.Product)
14 .ToListAsync();
15
16// Projections
17var productDtos = await _context.Products
18 .Select(p => new ProductDto(p.Id, p.Name, p.Price, p.Category.Name))
19 .ToListAsync();
20
21// Pagination
22var pagedProducts = await _context.Products
23 .OrderBy(p => p.Name)
24 .Skip((page - 1) * pageSize)
25 .Take(pageSize)
26 .ToListAsync();
27
28// Complex query
29var topSellingProducts = await _context.OrderItems
30 .GroupBy(oi => oi.ProductId)
31 .Select(g => new
32 {
33 ProductId = g.Key,
34 TotalSold = g.Sum(oi => oi.Quantity),
35 Revenue = g.Sum(oi => oi.Quantity * oi.UnitPrice)
36 })
37 .OrderByDescending(x => x.TotalSold)
38 .Take(10)
39 .ToListAsync();Apply these patterns for performant EF Core usage:
1# Create initial migration
2dotnet ef migrations add InitialCreate
3
4# Apply migrations to database
5dotnet ef database update
6
7# Generate SQL script
8dotnet ef migrations script -o migration.sql
9
10# Remove last migration (if not applied)
11dotnet ef migrations remove
12
13# Revert to specific migration
14dotnet ef database update MigrationName
15
16# Migration file example
17public partial class AddProductReviewsTable : Migration
18{
19 protected override void Up(MigrationBuilder migrationBuilder)
20 {
21 migrationBuilder.CreateTable(
22 name: "ProductReviews",
23 columns: table => new
24 {
25 Id = table.Column<int>(nullable: false)
26 .Annotation("Npgsql:ValueGenerationStrategy",
27 NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
28 ProductId = table.Column<int>(nullable: false),
29 Rating = table.Column<int>(nullable: false),
30 Comment = table.Column<string>(maxLength: 1000, nullable: true),
31 CreatedAt = table.Column<DateTime>(nullable: false)
32 },
33 constraints: table =>
34 {
35 table.PrimaryKey("PK_ProductReviews", x => x.Id);
36 table.ForeignKey("FK_ProductReviews_Products", x => x.ProductId,
37 "Products", "Id", onDelete: ReferentialAction.Cascade);
38 });
39 }
40
41 protected override void Down(MigrationBuilder migrationBuilder)
42 {
43 migrationBuilder.DropTable(name: "ProductReviews");
44 }
45}Your progress is saved in this browser, on this device only.