Entity Framework Core with Code First

Programming
Published on April 20, 2022

Code-First with Minimum Configuration

This example uses SQL Server.

1Install Packages

Create a new console application and install two packages.

Install-Package Microsoft.EntityFrameworkCore.Tools
Install-Package Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.EntityFrameworkCore.Tools
    Used to execute Entity Framework commands in the Package Manager Console.
  • Microsoft.EntityFrameworkCore.SqlServer
    The provider library for using SQL Server.

2Create DbContext

Entity Framework will automatically detect a class inheriting DbContext, allowing you to perform code-first operations. No specific code needs to be written at the program's entry point. However, if there are no tables to be created, effectively nothing will be executed. Therefore, create a Hoge class and add a property of type DbSet<Hoge> to the class inheriting DbContext.

public class AppDbContext : DbContext
{
    public DbSet<Hoge> Hoge { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder options)
        => options.UseSqlServer("<接続文字列>"); // <Connection String>
}

public class Hoge
{
    public int Id { get; set; }
    public string Name { get; set; }
}

3Create a Migration

  • Build the project
  • Set the console app as the startup project
  • Open the Package Manager Console and select the console app as the default project

Once these conditions are met, execute the following command:

Add-Migration hoge

If the output is as follows, it's OK:

PM> Add-Migration hoge
Build started...
Build succeeded.
To undo this action, use Remove-Migration.

The automatically generated source file will be displayed. The file name will be yyyyMMddHHmmss_<migration_name>.cs. This file is created in a folder named Migrations, which is automatically generated. This C# code defines the database changes.

4Apply the Migration to the Database

Next, execute the following command:

Update-Database

If the output is as follows, the application is complete:

PM> Update-Database
Build started...
Build succeeded.
Acquiring an exclusive lock for migration application. See https://aka.ms/efcore-docs-migrations-lock for more information if this takes too long.
Applying migration 'yyyyMMddHHmmss_hoge'.
Done.

If you connect to the database, you can confirm that the Hoge table has been created.

In Case of a Web Application

Similar to console applications, code-first can be used in a single project, but it is better to split the projects as follows:

  • (A) ASP.NET Core application
  • (B) Data access library
  • (C) Model (table) definition

The project dependencies are as follows:
(A) ⇒ (B), (C)
(B) ⇒ (C)

If implementing in a single project, simply create each of the following code pieces within that one project.

(A) ASP.NET Core Application

The method remains the same for both Razor Pages and MVC.

  • Startup project
  • Program.cs (add DbContext DI settings)
  • appsettings.json (connection string)

Example: Adding DbContext (PostgreSQL)

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

// Add DbContext DI settings
builder.Services.AddDbContext<DefaultDbContext>(options =>
{
    options.UseNpgsql(builder.Configuration.GetDefaultConnectionString());
    options.EnableSensitiveDataLogging();
});

Example: Connection string

// appsettings.json
{
  "ConnectionStrings": {
    "DefaultConnection": "Host=localhost;Database=timecard;Username=postgres;Password=hoge;Port=5432"
  },

(B) Data Access Library

  • Migrations (*.cs) auto-generated
  • DefaultDbContext.cs
  • DefaultDbContext.DbSet.cs
  • DefaultDbContext.OnModelCreating.cs
  • Database provider (select according to the database used)
    • Microsoft.EntityFrameworkCore.SqlServer
    • Microsoft.EntityFrameworkCore.Sqlite
    • Npgsql.EntityFrameworkCore.PostgreSQL
    • Oracle.EntityFrameworkCore

It is often easier to maintain if you make the DbContext class partial and separate it by purpose.

Example: DefaultDbContext (PostgreSQL)

// DefaultDbContext.cs
public partial class DefaultDbContext : DbContext
{
    public DefaultDbContext(DbContextOptions options)
        : base(options)
    {
        AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
    }
}

// DefaultDbContext.DbSet.cs
public partial class DefaultDbContext
{
    public DbSet<User> User { get; set; }
    public DbSet<UserCard> UserCard { get; set; }
    public DbSet<TimeCard> TimeCard { get; set; }
}

// DefaultDbContext.OnModelCreating.cs
public partial class DefaultDbContext
{
    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        builder.Entity<User>(e => { e.HasKey(e => new { e.UserId }); });
        builder.Entity<UserCard>(e => { e.HasKey(e => new { e.UserId, e.CardNumber }); });
        builder.Entity<TimeCard>(e => { e.HasKey(e => new { e.UserId, e.Date }); });

        // Convert names to SnakeCase
        builder.ToSnakeNames();
    }
}

(C) Model (Table) Definition

  • Models/Entities (*.cs)

Create the model classes that will become tables here.

(A)'s ViewModel/PageModel: Definition of screen display items
(C)'s Model: Definition of database tables

The key point is to completely separate these roles and establish the dependency (A) ⇒ (C), so that even if there are changes to the screen display specifications, the database table definitions are not directly affected.

Creating Migrations and Applying to the Database

  1. Create/Modify models
  2. Build the solution
  3. Display the Package Manager Console
  4. Confirm that "(B) Data Access Library" is selected in the Package Manager Console's "Default project"
  5. Confirm that "(A) ASP.NET Core Application" is selected as the startup project
  6. Execute the following command
Add-Migration <migration_name>

<migration_name> is arbitrary but must be unique within the Migrations folder. 1

At this point, a *.cs file defining the changes will be created in the (B) Data Access Library/Migrations folder.

  1. Execute the following command:
Update-Database

Model changes will be applied to the database.

Footnotes

  1. The file name will be yyyyMMddHHmmss_<migration_name>.cs, but the class name in the code will not have a timestamp. You will execute Add-Migration each time you apply model changes to the database, but the same <migration_name> cannot be used multiple times, so it's a good idea to decide on a naming convention for migration names in advance.