# DbContext and Entities

EF Core maps entity classes to tables, matching names by convention. A DbContext class lists a DbSet for each table and holds the connection. In ASP.NET Core, register it with AddDbContext and UseSqlServer, and controllers ask for it like any other service.

Journeyman · C# · Entity Framework Core · Updated November 3, 2026

## After this lesson you can

- Write entity classes and a DbContext with a DbSet for each table of an existing database
- Register a DbContext with AddDbContext and UseSqlServer, and ask for it in a controller

## Key takeaways

- An entity is a class EF Core maps to a table: one object per row, and one property per column with the same name.
- By convention, a property named `Id`, or the class name plus `Id` such as `ProductId`, is the primary key.
- Property types must match the columns: a nullable column needs a nullable property such as `int?`, or a `null` throws an exception.
- A `DbContext` stands for the database, with a `DbSet<T>` property for each table, such as `DbSet<Product> Products`.
- Register the context with `AddDbContext` and `UseSqlServer`, and a controller asks for it in its constructor, as it asked for a DAO.

EF Core needs two things from you: classes that describe the tables, and a class that describes the database.

## The package

Add the **Microsoft.EntityFrameworkCore.SqlServer** package (it brings EF Core and the SQL Server provider). As with Microsoft.Data.SqlClient, also add a current **System.Security.Cryptography.Pkcs**, or the build warns about an older version with a known vulnerability:

```xml
<ItemGroup>
  <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.14" />
  <PackageReference Include="System.Security.Cryptography.Pkcs" Version="10.0.14" />
</ItemGroup>
```

## Entities

An **entity** is a class EF Core maps to a table: one object per row, one property per column. They're the same plain classes you used with Dapper:

```csharp
using System.ComponentModel.DataAnnotations.Schema;

public class Product
{
    public int ProductId { get; set; }
    public string ProductName { get; set; } = "";
    public int? CategoryId { get; set; }

    [Column(TypeName = "money")]
    public decimal? UnitPrice { get; set; }

    public short? UnitsInStock { get; set; }
    public bool Discontinued { get; set; }
}

public class Category
{
    public int CategoryId { get; set; }
    public string CategoryName { get; set; } = "";
    public string? Description { get; set; }

    public List<Product> Products { get; set; } = new List<Product>();
}
```

EF Core works most things out by **convention**:

- **The key:** a property named `Id`, or the class name plus `Id` (`ProductId`), is the primary key.
- **The columns:** each property maps to the column with the same name. Columns you leave out, such as `QuantityPerUnit`, are simply ignored.
- **The types:** C# types match SQL types: `int?` for a nullable `int` column, `short?` for a nullable `smallint`, `bool` for `bit`. Get these right: a `null` in a column whose property can't hold one throws an exception.
- **Relationships:** `Category.Products` and `Product.CategoryId` tell EF Core that a category has many products, joined on `CategoryId`. A property like `Products` is called a **navigation property**.

Where a convention isn't enough, an attribute says more. `[Column(TypeName = "money")]` tells EF Core the column is SQL Server's `money` type, so it stores prices correctly. (`[Table("Products")]` and `[Key]` exist too, for names that don't follow the conventions.)

## The DbContext

A **`DbContext`** stands for the database. Derive your own, with a **`DbSet<T>`** property for each table:

```csharp
using Microsoft.EntityFrameworkCore;

public class NorthwindContext : DbContext
{
    public NorthwindContext(DbContextOptions<NorthwindContext> options) : base(options)
    {
    }

    public DbSet<Product> Products => Set<Product>();
    public DbSet<Category> Categories => Set<Category>();
}
```

- The **`DbSet<Product>`** named `Products` maps to the `Products` table. Everything you do with products goes through it: queries, adds, removes.
- The **constructor** takes the **options**, which say which database to use and how to reach it, and passes them to `DbContext`.

## Registering it in ASP.NET Core

In `Program.cs`, **`AddDbContext`** registers the context, and **`UseSqlServer`** gives it the connection string:

```csharp
string connectionString = builder.Configuration.GetConnectionString("Northwind")!;
builder.Services.AddDbContext<NorthwindContext>(options => options.UseSqlServer(connectionString));
```

The context is registered **scoped**, like your DAOs: one per request, with its own connection, thrown away when the request ends.

A controller asks for it in its constructor, as it asked for a DAO:

```csharp
[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
    private readonly NorthwindContext db;

    public ProductsController(NorthwindContext db)
    {
        this.db = db;
    }

    [HttpGet("count")]
    public async Task<int> Count()
    {
        return await db.Products.CountAsync();
    }
}
```

`GET /api/products/count` answers `77`. In fact, the `DbContext` plays the DAO's part: it's the class that talks to the database.

## Trying it in a console app

Outside ASP.NET Core, build the options yourself and create the context with `new`. It's handy for experiments:

```csharp
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;

string connectionString = "Server=localhost;Database=Northwind;Integrated Security=true;TrustServerCertificate=true;";
DbContextOptions<NorthwindContext> options = new DbContextOptionsBuilder<NorthwindContext>()
    .UseSqlServer(connectionString)
    .Options;

using NorthwindContext db = new NorthwindContext(options);

Console.WriteLine($"{await db.Products.CountAsync()} products");
Console.WriteLine($"{await db.Categories.CountAsync()} categories");

public class Product
{
    public int ProductId { get; set; }
    public string ProductName { get; set; } = "";
    public int? CategoryId { get; set; }

    [Column(TypeName = "money")]
    public decimal? UnitPrice { get; set; }

    public short? UnitsInStock { get; set; }
    public bool Discontinued { get; set; }
}

public class Category
{
    public int CategoryId { get; set; }
    public string CategoryName { get; set; } = "";
    public string? Description { get; set; }

    public List<Product> Products { get; set; } = new List<Product>();
}

public class NorthwindContext : DbContext
{
    public NorthwindContext(DbContextOptions<NorthwindContext> options) : base(options)
    {
    }

    public DbSet<Product> Products => Set<Product>();
    public DbSet<Category> Categories => Set<Category>();
}
```

```text
77 products
8 categories
```

`using` disposes the context when the program ends, closing its connection, just like a `SqlConnection`.

## Check yourself

2 questions to try on the lesson page.

## So what

Entities describe the rows, the `DbContext` describes the database, and `AddDbContext` hands it to whoever asks. With those three pieces in place, every table is a `DbSet` you can query with LINQ.

## Key terms

- Entity
- DbContext
- DbSet
- Primary Key Convention
- Navigation Property

## Cheat sheets

- [DbContext and Entities](https://www.ask-codey.com/cheat-sheets/entity-framework-core-dbcontext-and-entities-cheat-sheet)

Source: https://www.ask-codey.com/lessons/entity-framework-core-dbcontext-and-entities
