Section 2 of 7

DbContext and Entities

🎯 What You'll Learn

  • What is DbContext
  • Creating entity classes
  • Configuring DbContext
  • Data annotations
  • Fluent API configuration
  • Dependency injection setup

What is DbContext?

DbContext is the primary class for interacting with the database. It:

  • Represents a session with the database
  • Manages entity instances and their state
  • Provides DbSet properties for querying
  • Tracks changes to entities
  • Saves changes to the database

Creating Entities

Entities are C# classes that represent database tables.

Models/Product.cs C#
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public decimal Price { get; set; }
    public string? Description { get; set; }
    public int Quantity { get; set; }
    public DateTime CreatedAt { get; set; }
}

Conventions

EF Core uses conventions to infer database schema:

Convention Result
Class name Table name (Products)
Property named Id or ProductId Primary key
string property nvarchar(max) column
int, long primary key Identity column (auto-increment)
Nullable type (int?) Nullable column

Creating DbContext

Data/InvenTrackDbContext.cs C#
public class InvenTrackDbContext : DbContext
{
    // DbSet properties represent tables
    public DbSet<Product> Products { get; set; }
    public DbSet<Category> Categories { get; set; }
    public DbSet<Order> Orders { get; set; }

    public InvenTrackDbContext(DbContextOptions<InvenTrackDbContext> options)
        : base(options)
    {
    }
}

Configuring DbContext

In Program.cs (Recommended)

Program.cs C#
var builder = WebApplication.CreateBuilder(args);

// Register DbContext with DI
builder.Services.AddDbContext<InvenTrackDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

var app = builder.Build();
app.Run();

Connection String in appsettings.json

appsettings.json JSON
{
  "ConnectionStrings": {
    "DefaultConnection": "Server=.;Database=InvenTrack;Trusted_Connection=True;TrustServerCertificate=True;"
  }
}

Data Annotations

Data Annotations configure entities using attributes.

Using Data Annotations C#
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

[Table("Products")]
public class Product
{
    [Key]
    public int Id { get; set; }

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

    [Column(TypeName = "decimal(18,2)")]
    public decimal Price { get; set; }

    [MaxLength(500)]
    public string? Description { get; set; }

    public int Quantity { get; set; }

    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}

Common Data Annotations

Annotation Purpose
[Key] Mark as primary key
[Required] Not null column
[MaxLength(100)] String length limit
[Column("ProductName")] Custom column name
[Table("Products")] Custom table name
[ForeignKey("CategoryId")] Foreign key
[NotMapped] Exclude from database

Fluent API Configuration

Fluent API provides more control than data annotations.

OnModelCreating C#
public class InvenTrackDbContext : DbContext
{
    public DbSet<Product> Products { get; set; }

    public InvenTrackDbContext(DbContextOptions<InvenTrackDbContext> options)
        : base(options) { }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Product>(entity =>
        {
            // Table name
            entity.ToTable("Products");

            // Primary key
            entity.HasKey(p => p.Id);

            // Properties
            entity.Property(p => p.Name)
                .IsRequired()
                .HasMaxLength(100);

            entity.Property(p => p.Price)
                .HasColumnType("decimal(18,2)");

            entity.Property(p => p.Description)
                .HasMaxLength(500);

            entity.Property(p => p.CreatedAt)
                .HasDefaultValueSql("GETUTCDATE()");

            // Index
            entity.HasIndex(p => p.Name);
        });
    }
}

Fluent API vs Data Annotations

Aspect Fluent API Data Annotations
Power More features Limited features
Readability Centralized On entity classes
Separation Clean entities Attributes on entities
Precedence Overrides annotations Lower priority

Using DbContext

In Controllers

Controllers/ProductsController.cs C#
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly InvenTrackDbContext _context;

    public ProductsController(InvenTrackDbContext context)
    {
        _context = context;
    }

    [HttpGet]
    public async Task<ActionResult> GetProducts()
    {
        var products = await _context.Products.ToListAsync();
        return Ok(products);
    }

    [HttpPost]
    public async Task<ActionResult> CreateProduct(Product product)
    {
        _context.Products.Add(product);
        await _context.SaveChangesAsync();
        
        return CreatedAtAction(nameof(GetProduct), new { id = product.Id }, product);
    }
}

Complete InvenTrack Example

Models/Product.cs C#
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public decimal Price { get; set; }
    public string? Description { get; set; }
    public int Quantity { get; set; }
    public int CategoryId { get; set; }
    public DateTime CreatedAt { get; set; }
}

public class Category
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
}
Data/InvenTrackDbContext.cs C#
public class InvenTrackDbContext : DbContext
{
    public DbSet<Product> Products { get; set; }
    public DbSet<Category> Categories { get; set; }

    public InvenTrackDbContext(DbContextOptions<InvenTrackDbContext> options)
        : base(options) { }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Product>(entity =>
        {
            entity.Property(p => p.Name).HasMaxLength(100);
            entity.Property(p => p.Price).HasColumnType("decimal(18,2)");
            entity.Property(p => p.CreatedAt).HasDefaultValueSql("GETUTCDATE()");
        });

        modelBuilder.Entity<Category>(entity =>
        {
            entity.Property(c => c.Name).HasMaxLength(50);
        });
    }
}
Program.cs C#
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddDbContext<InvenTrackDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

var app = builder.Build();

app.MapControllers();
app.Run();

Best Practices

  • Use DI: Register DbContext with dependency injection
  • Connection strings: Store in appsettings.json
  • Async operations: Always use async methods
  • Fluent API: Prefer for complex configurations
  • Data annotations: Use for simple validation
  • DbSet properties: Name as plural (Products, not Product)
  • Dispose: DI handles disposal automatically

Key Takeaways

  • DbContext: Central class for database operations
  • Entities: C# classes that map to tables
  • DbSet: Properties representing tables
  • Conventions: EF Core infers schema from code
  • Data Annotations: Attributes for configuration
  • Fluent API: More powerful configuration in OnModelCreating
  • DI: Register with AddDbContext
  • Connection strings: Configure in appsettings.json
🎯 Next Steps

You now understand DbContext and entities! In the next section, we'll explore Code-First Migrations—how to create and manage your database schema from code.