マイグレーション(Migration)とは、データベースの構造変更を安全かつ自動的に行うためのEntity Frameworkの機能です。
C#のモデルクラス(設計図)の変更を検出し、それに合わせてデータベースの実際のテーブル構造を更新するプロセスを指します。「移行」という意味の通り、データベースを現在の状態から新しい状態へと段階的に移行させる仕組みです。この機能を利用することで、データベースに対する操作やSQLの作成と実行と言った、従来だと当たり前のように行っていた作業を簡素化することが可能です。
最近はBlazor絡みのシステムに携わることが多いのですが、データベースをいじるときはEntity Framework(以下、EF)を使っています。今回はこのEFを利用する際に必要になるマイグレーションに関して、基本的な知識や実行方法についてまとめてみました。
マイグレーションを始める前に、まずはBlazorプロジェクトにEntity Framework Core (EF Core) を導入し、データベースと通信できるように設定しましょう。ここが全ての土台となります。
Visual Studioの「パッケージマネージャーコンソール」で以下のコマンドを実行し、EF Coreに必要なツールをインストールします。
# データベースプロバイダー (ここではSQL Server)
Install-Package Microsoft.EntityFrameworkCore.SqlServer
# EF Coreのコマンドラインツール
Install-Package Microsoft.EntityFrameworkCore.Tools
データベースの場所や認証情報を定義します。プロジェクトルートにあるappsettings.json
ファイルに、以下のようにConnectionStrings
を追加します。
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=MyBlazorAppDb;Trusted_Connection=True;"
},
"Logging": {
// ...
},
"AllowedHosts": "*"
}
Server=(localdb)\\mssqllocaldb
: Visual Studioと一緒にインストールされる開発用のSQL Serverです。Database=MyBlazorAppDb
: これから作成するデータベース名です。アプリケーション全体でデータベース接続を使い回せるように、Program.cs
にDbContext
を「サービス」として登録します。これは「依存性の注入(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. モデルクラスの作成
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分
手順の流れ:
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のマイグレーション機能を使用することで、この作業を劇的に簡素化し、本来の業務ロジックの実装に集中できるようになります。
EFは、現在のモデルクラス(C#のクラス)と前回のスナップショットを比較し、何が変更されたかを自動的に検出します。
変更内容に基づいて、データベースを更新するためのC#コードが自動生成されます。このファイルには以下の情報が含まれます:
生成されたマイグレーションファイルを実行することで、実際のデータベースが更新されます。
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
コマンドを明示的に実行する方法がより安全で推奨されます。
Visual Studio 2022では、パッケージマネージャーコンソールから以下のコマンドが使用できます:
基本的なマイグレーション操作:
# マイグレーションの追加
Add-Migration MigrationName
# データベースの更新
Update-Database
# 特定のマイグレーションまで戻す
Update-Database -Migration PreviousMigrationName
# マイグレーション一覧の表示
Get-Migration
# マイグレーションの削除(最新のもののみ)
Remove-Migration
プロジェクトフォルダで以下のコマンドが使用できます:
基本的なマイグレーション操作:
# マイグレーションの追加
dotnet ef migrations add MigrationName
# データベースの更新
dotnet ef database update
# 特定のマイグレーションまで戻す
dotnet ef database update PreviousMigrationName
# マイグレーション一覧の表示
dotnet ef migrations list
# マイグレーションの削除(最新のもののみ)
dotnet ef migrations remove
生成されたマイグレーションファイルは「Migrations」フォルダに格納され、内容を直接確認・編集できます。
マイグレーションで作成したデータベースを、実際に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アプリケーション開発において、データベースの進化は避けられません。マイグレーション機能を習得することで、変化に柔軟に対応できる堅牢なシステムを構築できるようになるでしょう。