The time has come: let's set up our DbContext
and DbSet
s
First thing we're going to do is install the Microsoft.EntityFrameworkCore.Sqlite
package. This will also install Microsoft.EntityFrameworkCore
as dependencies.
Setting up your DbContext
Setting up our initial DbContext
is pretty simple - we really just need to create a new class called DbContext
.
Many .NETters like to put this in a folder called Data or Infrastructure - we're just gonna keep it in the root of the project for now. I don't have a strong opinion because organization is a personal (and team thing)!
using Microsoft.EntityFrameworkCore;
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
public DbSet<Employee> Employees { get; set; }
}
Let's break down the AppDbContext
class:
- It inherits from
DbContext
, which gives it all of its goodness - It has a constructor that takes a
DbContextOptions<AppDbContext>
- these options are our configuration for theDbContext
, usually the dataset - It has a
DbSet<Employee> Employees { get; set; }
property - this is the collection ofEmployee
entities that are mapped to theEmployees
table in the database
Setting up your DbContext with dependency injection
We need to set up our DbContext
with dependency injection. This is pretty simple - we just need to add it to the IServiceCollection
in the ConfigureServices
method in Program.cs
:
using Microsoft.EntityFrameworkCore;
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection")));
Wait, what's the builder.Configuration.GetConnectionString("DefaultConnection")
? That's a connection string to our database. We need to add that to our appsettings.json
:
{
"ConnectionStrings": {
"DefaultConnection": "Data Source=employees.db"
}
}
Setting up your DbContext with Migrations
Migrations are a feature of Entity Framework Core that allows us to make changes to our database schema over time. This is useful for a few reasons:
- We can make changes to our database schema without dropping and recreating the database
- We can make changes to our database schema without manually writing SQL (for the most part - more on that later)
We'll use the dotnet ef
CLI to manage our migrations.
First, we need to add the Microsoft.EntityFrameworkCore.Tools
package to our project. We'll install the tools globally.
dotnet tool install --global dotnet-ef
Then we can start running commands to manage our migrations. We'll start first by creating our database by running:
dotnet ef database update
This will create a new database called employees.db. It does a few other things as well:
❯ dotnet ef database update
Build started...
Build succeeded.
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (4ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
PRAGMA journal_mode = 'wal';
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE "__EFMigrationsHistory" (
"MigrationId" TEXT NOT NULL CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY,
"ProductVersion" TEXT NOT NULL
);
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT COUNT(*) FROM "sqlite_master" WHERE "name" = '__EFMigrationsHistory' AND "type" = 'table';
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT "MigrationId", "ProductVersion"
FROM "__EFMigrationsHistory"
ORDER BY "MigrationId";
info: Microsoft.EntityFrameworkCore.Migrations[20405]
No migrations were applied. The database is already up to date.
No migrations were applied. The database is already up to date.
Done.
Mainly, creates a database table for us to store our migrations history.
Let's add an extension for VS Code that helps us look at our database: SQLTools. It's popular and straightforward. We'll also install the SQLite SQLTools extension (and the driver, and Node tools, so that this can all run together.)
Once we get connected to the database, we can see that our __EFMigrationsHistory table is empty. Let's change that!
In terminal, run:
dotnet ef migrations add Init
This will create a new migration file in the Migrations
folder. Let's take a look at it:
public partial class Init : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Employees",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
FirstName = table.Column<string>(type: "TEXT", nullable: false),
LastName = table.Column<string>(type: "TEXT", nullable: false),
SocialSecurityNumber = table.Column<string>(type: "TEXT", nullable: true),
Address1 = table.Column<string>(type: "TEXT", nullable: true),
Address2 = table.Column<string>(type: "TEXT", nullable: true),
City = table.Column<string>(type: "TEXT", nullable: true),
State = table.Column<string>(type: "TEXT", nullable: true),
ZipCode = table.Column<string>(type: "TEXT", nullable: true),
PhoneNumber = table.Column<string>(type: "TEXT", nullable: true),
Email = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Employees", x => x.Id);
});
migrationBuilder.CreateTable(
name: "EmployeeBenefits",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
EmployeeId = table.Column<int>(type: "INTEGER", nullable: false),
BenefitType = table.Column<int>(type: "INTEGER", nullable: false),
Cost = table.Column<decimal>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_EmployeeBenefits", x => x.Id);
table.ForeignKey(
name: "FK_EmployeeBenefits_Employees_EmployeeId",
column: x => x.EmployeeId,
principalTable: "Employees",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_EmployeeBenefits_EmployeeId",
table: "EmployeeBenefits",
column: "EmployeeId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "EmployeeBenefits");
migrationBuilder.DropTable(
name: "Employees");
}
}
Let's break down what's happening here:
- The
Up
method is where we define the changes we want to make to our database schema - The
Down
method is where we define the changes we want to make to our database schema if we want to revert the changes - The
migrationBuilder
parameter is where we define our table and column changes
The data types that we have migrations for are all defined by the properties in our Employee
class as well as the properties of the database we're using.
Note that it even created a EmployeeBenefits
table for us, as our property in the Employee
class has the List<EmployeeBenefit>
defined.
Let's use this opportunity to discuss navigation properties.
Navigation properties
Navigation properties are a feature of Entity Framework Core that allows us to navigate between related entities. In our case, we have a one-to-many relationship between Employee
and EmployeeBenefit
. This means that an employee can have many benefits, but a benefit can only belong to one employee.
In Employee
, we have a List<EmployeeBenefit> Benefits { get; set; }
property. This is a navigation property that allows us to navigate from an Employee
to its EmployeeBenefit
s.
We don't need a corresponding Employee
property in EmployeeBenefit
per se, but we're going to add it anyways because it's good practice.
🌶️🌶️🌶️ We have nullability turned on for our project, which has some interesting implications for EF Core. Note the weird = null!
syntax below - you can call this a bit of a compromise to make the nullability feature work nicely with EF Core. See more here.
public class EmployeeBenefits
{
public int Id { get; set; }
public int EmployeeId { get; set; }
public BenefitType BenefitType { get; set; }
public decimal Cost { get; set; }
public Employee Employee { get; set; } = null!;
}
We didn't make any changes that should have an impact with EF Core, but sometimes I like to run a new migration to make sure that everything is working as expected.
public partial class meeowwwww : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
Good, nothing changed - you can run this to remove the migration:
dotnet ef migrations remove
This will remove the last migration from the migrations history.
Run dotnet ef database update
again to apply the migrations. Look in SQLTools to see the changes!
We haven't connected this to our Employees endpoints yet - that will happen in the next couple of sections.
Connecting to the DB with SQLTools
Open the SQLTools Extension for VSCode and create a connection to the employees.db
database. When you do this, click to Test the Connection. VSCode may ask you to enable some features and restart VSCode. Follow the prompts and recreate the connection if need be after it restarts.