頑張らないために頑張る

ゆるく頑張ります

Entity Framework マイグレーションの基本

Posted at — Aug 22, 2025

概要

マイグレーションとは何か

マイグレーション(Migration)とは、データベースの構造変更を安全かつ自動的に行うためのEntity Frameworkの機能です。

C#のモデルクラス(設計図)の変更を検出し、それに合わせてデータベースの実際のテーブル構造を更新するプロセスを指します。「移行」という意味の通り、データベースを現在の状態から新しい状態へと段階的に移行させる仕組みです。この機能を利用することで、データベースに対する操作やSQLの作成と実行と言った、従来だと当たり前のように行っていた作業を簡素化することが可能です。

最近はBlazor絡みのシステムに携わることが多いのですが、データベースをいじるときはEntity Framework(以下、EF)を使っています。今回はこのEFを利用する際に必要になるマイグレーションに関して、基本的な知識や実行方法についてまとめてみました。

EF&マイグレーションのメリット

事前準備:プロジェクトにEF Coreを導入する

マイグレーションを始める前に、まずはBlazorプロジェクトにEntity Framework Core (EF Core) を導入し、データベースと通信できるように設定しましょう。ここが全ての土台となります。

ステップ1:必要なパッケージのインストール

Visual Studioの「パッケージマネージャーコンソール」で以下のコマンドを実行し、EF Coreに必要なツールをインストールします。

# データベースプロバイダー (ここではSQL Server)
Install-Package Microsoft.EntityFrameworkCore.SqlServer

# EF Coreのコマンドラインツール
Install-Package Microsoft.EntityFrameworkCore.Tools

ステップ2:接続文字列の設定

データベースの場所や認証情報を定義します。プロジェクトルートにあるappsettings.jsonファイルに、以下のようにConnectionStringsを追加します。

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=MyBlazorAppDb;Trusted_Connection=True;"
  },
  "Logging": {
    // ...
  },
  "AllowedHosts": "*"
}

ステップ3:DbContextの登録

アプリケーション全体でデータベース接続を使い回せるように、Program.csDbContextを「サービス」として登録します。これは「依存性の注入(DI)」と呼ばれる仕組みで、「必要な時に自動でDbContextを準備してください」と.NETにお願いするおまじないのようなものだと思ってください。

Program.csファイルを開き、builder.Build()の前に以下のコードを追加してください。

// using Microsoft.EntityFrameworkCore; をファイルの先頭に追加

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));

var app = builder.Build();

これで、マイグレーションを実行する準備が整いました。

マイグレーションの必要性を例で理解する

アプリケーションを開発する際、多くの場合でデータベースを使用してデータの保存や取得を行います。ここでは、Blazorでwebアプリケーション「商品管理システム」の構築を例に、Entity Framework(以下EF)とマイグレーションの有無による開発手順の違いを比較してみます。

想定シナリオ:商品管理システムの構築

以下の要件を満たすwebアプリケーションを作成するとします。

  1. 商品データベースとテーブルを新規作成
  2. 商品情報(ID、商品名、価格)を管理
  3. 商品の登録、検索機能を実装

方法1:Entity Frameworkとマイグレーションを使用した場合

手順の流れ

1. モデルクラスの作成

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

2. DbContextクラスの作成

public class ShopDbContext : DbContext
{
    public ShopDbContext(DbContextOptions<ShopDbContext> options) : base(options) { }
    
    public DbSet<Product> Products { get; set; }
}

3. データベース作成とテーブル生成

# コマンドライン
dotnet ef migrations add CreateProductTable
dotnet ef database update
# Visual Studio パッケージマネージャーコンソール
Add-Migration CreateProductTable
Update-Database

4. データ操作の実装

// 商品登録
var product = new Product { Name = "ノートパソコン", Price = 98000 };
context.Products.Add(product);
await context.SaveChangesAsync();

// 商品検索
var products = await context.Products
    .Where(p => p.Price < 100000)
    .ToListAsync();

所要時間: 約20分

方法2:Entity Frameworkとマイグレーションを使用しない場合

手順の流れ

1. データベース管理システムでの手動作業

-- SQL Server Management Studio等で手動実行
CREATE DATABASE ShopDatabase;
USE ShopDatabase;

CREATE TABLE Products (
    Id int IDENTITY(1,1) PRIMARY KEY,
    Name nvarchar(255) NOT NULL,
    Price decimal(18,2) NOT NULL
);

2. 接続文字列の設定

// 手動でConnectionStringを設定
string connectionString = "Server=.;Database=ShopDatabase;Trusted_Connection=true;";

3. データアクセス層の手動実装

public class ProductRepository
{
    private readonly string _connectionString;
    
    public ProductRepository(string connectionString)
    {
        _connectionString = connectionString;
    }
    
    // 商品登録
    public async Task AddProductAsync(Product product)
    {
        using var connection = new SqlConnection(_connectionString);
        await connection.OpenAsync();
        
        var command = new SqlCommand(
            "INSERT INTO Products (Name, Price) VALUES (@name, @price)", 
            connection);
        command.Parameters.AddWithValue("@name", product.Name);
        command.Parameters.AddWithValue("@price", product.Price);
        
        await command.ExecuteNonQueryAsync();
    }
    
    // 商品検索
    public async Task<List<Product>> GetProductsAsync(decimal maxPrice)
    {
        var products = new List<Product>();
        
        using var connection = new SqlConnection(_connectionString);
        await connection.OpenAsync();
        
        var command = new SqlCommand(
            "SELECT Id, Name, Price FROM Products WHERE Price < @maxPrice", 
            connection);
        command.Parameters.AddWithValue("@maxPrice", maxPrice);
        
        using var reader = await command.ExecuteReaderAsync();
        while (await reader.ReadAsync())
        {
            products.Add(new Product
            {
                Id = reader.GetInt32("Id"),
                Name = reader.GetString("Name"),
                Price = reader.GetDecimal("Price")
            });
        }
        
        return products;
    }
}

4. 依存関係注入の設定

// Program.cs
builder.Services.AddScoped<ProductRepository>(provider => 
    new ProductRepository(builder.Configuration.GetConnectionString("DefaultConnection")));

所要時間: 約2-3時間

比較結果:マイグレーション使用のメリット

項目 マイグレーション使用 マイグレーション未使用
開発時間 20分 2-3時間
コード量 最小限 大量のボイラープレート
エラー発生率 低い(自動生成) 高い(手動実装)
保守性 高い 低い

具体的な開発上のメリット

1. 圧倒的な時間短縮 - データベース作成からデータ操作まで20分で完了 - SQLの手動記述やデータアクセス層の実装が不要

2. エラーの大幅な削減 - タイピングミスによるSQL文法エラーの回避 - パラメータ設定ミスの防止 - 型安全性の確保

3. 保守性の向上 - モデルクラスの変更だけでデータベース構造を更新可能 - 複雑なSQL文の管理が不要 - チーム開発時の一貫性確保

4. 学習コストの削減 - SQL文の詳細な知識が不要 - C#の知識だけで完結 - LINQ による直感的なデータ操作

従来のシステム開発では、データベースの作成と操作に多くの時間と労力を要していましたが、Entity Frameworkのマイグレーション機能を使用することで、この作業を劇的に簡素化し、本来の業務ロジックの実装に集中できるようになります。

マイグレーションの仕組み

1. モデルクラスの変更検出

EFは、現在のモデルクラス(C#のクラス)と前回のスナップショットを比較し、何が変更されたかを自動的に検出します。

2. マイグレーションファイルの生成

変更内容に基づいて、データベースを更新するためのC#コードが自動生成されます。このファイルには以下の情報が含まれます:

3. データベースへの適用

生成されたマイグレーションファイルを実行することで、実際のデータベースが更新されます。

実際の開発での活用例

シナリオ:新規データベースの作成

状況

Blazorプロジェクトを新規作成し、Entity Frameworkを使ってデータベースを初めて構築したい

手順

1. モデルクラスを作成

Customer.csというファイルを作成します。これがデータベースのテーブル設計図になります。

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

2. DbContextクラスを作成 ApplicationDbContext.csというファイルを作成します。これはデータベース全体との通信を担う窓口のような役割を果たします。

using Microsoft.EntityFrameworkCore;

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

    // Customerテーブルを操作するための道具
    public DbSet<Customer> Customers { get; set; }
}

3. マイグレーションコマンドの実行 準備ができたので、パッケージマネージャーコンソールでコマンドを実行します。

# 設計図(モデル)からデータベースの変更点(マイグレーション)を作成
Add-Migration InitialCreate

# 変更点を実際のデータベースに適用
Update-Database

重要なポイント: - 初回マイグレーション名は慣例的に「InitialCreate」とすることが多いです。 - Update-Databaseを実行した時点で、appsettings.jsonで指定した名前のデータベースが物理的に作成されます。

シナリオ:新しい項目の追加

状況

顧客管理システムに「生年月日」フィールドを追加したい。

手順

// 1. モデルクラスを更新
public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public DateTime? BirthDate { get; set; } // 新規追加
}

コマンドライン(bash/PowerShell)の場合:

# 2. マイグレーションを生成
dotnet ef migrations add AddBirthDateToCustomer

# 3. データベースに適用
dotnet ef database update

Visual Studio 2022のパッケージマネージャーコンソールの場合:

# 2. マイグレーションを生成
Add-Migration AddBirthDateToCustomer

# 3. データベースに適用
Update-Database

シナリオ:項目名の変更

状況

「Email」を「EmailAddress」に変更したい。しかし、既存のデータを失いたくない。

EFはデフォルトでこれを「Email列の削除」と「EmailAddress列の追加」として認識するため、そのまま適用するとデータが失われてしまいます。 これを防ぐため、生成されたマイグレーションファイルを手動で修正します。

手順

// 1. モデルクラスのプロパティ名を変更
public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string EmailAddress { get; set; } // Email -> EmailAddress に変更
    public DateTime? BirthDate { get; set; }
}
# 2. マイグレーションを生成(パッケージマネージャーコンソール)
Add-Migration RenameEmailToEmailAddress

3. 生成されたマイグレーションファイルを修正

Migrationsフォルダに生成されたxxxxx_RenameEmailToEmailAddress.csファイルを開きます。 初期状態では、DropColumn(列の削除)とAddColumn(列の追加)になっています。

修正前のコード(データが失われる!)

protected override void Up(MigrationBuilder migrationBuilder)
{
    // EFは列の削除と追加として認識する
    migrationBuilder.DropColumn(
        name: "Email",
        table: "Customers");

    migrationBuilder.AddColumn<string>(
        name: "EmailAddress",
        table: "Customers",
        type: "nvarchar(max)",
        nullable: false,
        defaultValue: "");
}

protected override void Down(MigrationBuilder migrationBuilder)
{
    migrationBuilder.DropColumn(
        name: "EmailAddress",
        table: "Customers");

    migrationBuilder.AddColumn<string>(
        name: "Email",
        table: "Customers",
        type: "nvarchar(max)",
        nullable: false,
        defaultValue: "");
}

これをRenameColumn(列名の変更)に手動で修正します。Downメソッドも忘れずに修正しましょう。

修正後のコード(安全)

protected override void Up(MigrationBuilder migrationBuilder)
{
    // DropColumnとAddColumnをRenameColumnに書き換える
    migrationBuilder.RenameColumn(
        name: "Email",
        table: "Customers",
        newName: "EmailAddress");
}

protected override void Down(MigrationBuilder migrationBuilder)
{
    // ロールバック処理もRenameColumnにする
    migrationBuilder.RenameColumn(
        name: "EmailAddress",
        table: "Customers",
        newName: "Email");
}
# 4. 修正したマイグレーションをデータベースに適用
Update-Database

この手順により、既存のデータを保持したまま、安全に列名を変更できます。

シナリオ:複数環境への展開

開発環境(Development)で作成・テストしたマイグレーションを、本番環境(Production)へ適用するケースです。環境ごとに異なるデータベース接続情報を使用します。

手順

1. 環境ごとの設定ファイルを作成 appsettings.jsonに加えて、appsettings.Production.jsonを作成し、本番用の接続文字列を記述します。

appsettings.json (開発用)

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=MyBlazorApp_Dev;Trusted_Connection=True;"
  },
  // ...
}

appsettings.Production.json (本番用)

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=your_production_server;Database=MyBlazorApp_Prod;User Id=your_user;Password=your_password;"
  }
}

2. .NET CLIで環境を指定してマイグレーションを適用 アプリケーションのデプロイ時やCI/CDパイプラインでは、.NET CLIを使い、--environmentオプションで適用先の環境を指定します。

# ターミナルで本番環境を指定してデータベースを更新
dotnet ef database update --environment Production

このコマンドを実行すると、Entity Frameworkはappsettings.Production.jsonの設定を読み込み、本番データベースに対してマイグレーションを適用します。

3. (参考) アプリケーション起動時に自動適用する方法 Program.csにコードを追加することで、アプリケーション起動時に未適用のマイグレーションを自動で実行させることも可能です。

// Program.cs
var app = builder.Build();

// スコープを作成してDbContextを取得
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    var context = services.GetRequiredService<ApplicationDbContext>();
    // 保留中のマイグレーションを適用
    context.Database.Migrate();
}

// ... 以降のコード
app.Run();

注意点: この方法は手軽ですが、複数のサーバーで同時にアプリが起動した場合などに問題が発生する可能性があります。本番環境では、デプロイスクリプトの一部として、前述の.NET CLIコマンドを明示的に実行する方法がより安全で推奨されます。

注意すべきポイント

  1. 本番環境での慎重な適用:本番環境では事前にバックアップを取り、メンテナンス時間を設けて実行することが重要です。
  2. データ損失のリスク:列の削除やデータ型の変更時は、既存データが失われる可能性があります。事前に十分な検証を行いましょう。
  3. パフォーマンスへの影響:大量のデータを持つテーブルの変更は時間がかかる場合があります。

Visual Studio 2022での実践

パッケージマネージャーコンソールの活用

Visual Studio 2022では、パッケージマネージャーコンソールから以下のコマンドが使用できます:

基本的なマイグレーション操作:

# マイグレーションの追加
Add-Migration MigrationName

# データベースの更新
Update-Database

# 特定のマイグレーションまで戻す
Update-Database -Migration PreviousMigrationName

# マイグレーション一覧の表示
Get-Migration

# マイグレーションの削除(最新のもののみ)
Remove-Migration

コマンドライン(.NET CLI)での実行

プロジェクトフォルダで以下のコマンドが使用できます:

基本的なマイグレーション操作:

# マイグレーションの追加
dotnet ef migrations add MigrationName

# データベースの更新
dotnet ef database update

# 特定のマイグレーションまで戻す
dotnet ef database update PreviousMigrationName

# マイグレーション一覧の表示
dotnet ef migrations list

# マイグレーションの削除(最新のもののみ)
dotnet ef migrations remove

どちらを使用すべきか?

ソリューションエクスプローラーでの確認

生成されたマイグレーションファイルは「Migrations」フォルダに格納され、内容を直接確認・編集できます。

発展:Blazorコンポーネントでデータを表示する

マイグレーションで作成したデータベースを、実際にBlazorの画面で使ってみます。Customerテーブルのデータを一覧表示する簡単なページを作成します。

1. Customer一覧ページを作成 PagesフォルダにCustomerList.razorというファイルを作成します。

@page "/customers"
@using Microsoft.EntityFrameworkCore
@inject ApplicationDbContext DbContext

<h3>顧客一覧</h3>

@if (customers == null)
{
    <p><em>読み込み中...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>名前</th>
                <th>Email</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var customer in customers)
            {
                <tr>
                    <td>@customer.Id</td>
                    <td>@customer.Name</td>
                    <td>@customer.Email</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private List<Customer> customers;

    // ページが初期化されるときに自動で実行されるメソッド
    protected override async Task OnInitializedAsync()
    {
        // DbContextを使ってデータベースから全顧客データを取得
        customers = await DbContext.Customers.ToListAsync();
    }
}

コードのポイント: * @inject ApplicationDbContext DbContext: Program.csで登録したDbContextを、このページで使えるようにします。 * OnInitializedAsync: ページが表示される前に、DbContextを通じてデータベースにアクセスし、顧客データを取得しています。 * @foreach: 取得した顧客リストをループ処理でテーブルに表示しています。

これで、アプリケーションを実行して/customersにアクセスすると、データベースに登録されている顧客情報が表示されます。 このように、マイグレーションはデータベースの準備を自動化し、開発者が本来集中すべき画面や機能の実装に専念できるようにしてくれる強力なツールなのです。

まとめ

Entity Frameworkのマイグレーション機能は、Blazorアプリケーション開発において欠かせない重要な機能です。適切に活用することで、データベース変更に伴うリスクを大幅に減らし、開発効率を向上させることができます。

自分のような初学者の方は、まずテンプレートに対する小さな変更から始めて、マイグレーションの動作を理解することをお勧めします。慣れてきたら、より複雑な変更にも挑戦してみてください。

現代のwebアプリケーション開発において、データベースの進化は避けられません。マイグレーション機能を習得することで、変化に柔軟に対応できる堅牢なシステムを構築できるようになるでしょう。

参考

  1. Entity Framework
  2. 移行の概要
  3. EF Core の使い方入門
  4. EF Core のマイグレーションがすごい便利だった
  5. Entity Framework のマイグレーションを基礎から理解する
comments powered by Disqus