概要
Entity Framework Core (EF Core) では、列挙型(Enum)のプロパティはデフォルトで整数(int)としてデータベースに保存されます。 しかし、データベースを直接参照した際の可読性を高めたり、既存の文字列カラムを持つデータベースと連携したりするために、Enumの「名前(文字列)」で保存したい場合があります。 本記事では、Fluent APIの HasConversion メソッドを使用して、Enumを文字列としてマッピングする方法を解説します。
仕様(入出力)
- 入力: Enum型プロパティを持つエンティティ。
- 処理:
HasConversion<string>()を適用し、値の変換ルールを定義する。 - 出力: データベースのカラム型がテキスト(TEXT, VARCHAR等)になり、Enumのメンバー名(”Active”, “Deleted” 等)が保存される。
基本の使い方
DbContext の OnModelCreating メソッド内で、対象のプロパティに対して変換設定を行います。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.Property(u => u.Status)
.HasConversion<string>() // 文字列として保存・読込を行う
.HasMaxLength(20); // カラムの最大長を指定(推奨)
}
コード全文
以下のコードは、タスク管理システムを想定し、タスクの状態(Status)を文字列としてSQLiteデータベースに保存・取得する完全なコンソールアプリケーションです。
※ 実行には Microsoft.EntityFrameworkCore.Sqlite パッケージが必要です。 dotnet add package Microsoft.EntityFrameworkCore.Sqlite
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
namespace EnumConversionSample
{
// 1. 列挙型の定義
public enum TaskStatus
{
Pending = 0, // 未着手
InProgress, // 進行中
Completed, // 完了
OnHold // 保留
}
// 2. エンティティクラスの定義
public class ProjectTask
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
// このプロパティを文字列としてDBに保存します
public TaskStatus Status { get; set; }
}
// 3. DbContextの定義
public class ProjectContext : DbContext
{
public DbSet<ProjectTask> Tasks { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// 動作確認用にSQLiteファイルを使用
optionsBuilder.UseSqlite("Data Source=tasks.db");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ProjectTask>()
.Property(t => t.Status)
.HasConversion<string>() // Enum <-> String の相互変換を設定
.HasMaxLength(20); // 文字列型の桁数を制限
}
}
class Program
{
static void Main(string[] args)
{
// データベースの初期化
using (var context = new ProjectContext())
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
}
// データの保存
using (var context = new ProjectContext())
{
var newTask = new ProjectTask
{
Title = "Design Database Schema",
Status = TaskStatus.InProgress // Enum値をセット
};
context.Tasks.Add(newTask);
context.SaveChanges();
Console.WriteLine("データを保存しました。");
Console.WriteLine($"保存したステータス: {newTask.Status}");
}
// データの読み込み
using (var context = new ProjectContext())
{
var task = context.Tasks.First();
Console.WriteLine("--- データベースから取得 ---");
Console.WriteLine($"ID: {task.Id}");
Console.WriteLine($"Title: {task.Title}");
// DBには "InProgress" という文字列が入っていますが、自動的にEnumに戻されます
Console.WriteLine($"Status: {task.Status} (型: {task.Status.GetType().Name})");
}
}
}
}
実行結果例
データを保存しました。
保存したステータス: InProgress
--- データベースから取得 ---
ID: 1
Title: Design Database Schema
Status: InProgress (型: TaskStatus)
カスタムポイント
- 文字数制限(HasMaxLength) デフォルトでは
nvarchar(max)やTEXTのような最大長になります。Enumの定義数や将来の拡張を考慮しつつ、HasMaxLength(50)のように適切な長さを設定することで、インデックス効率やストレージ効率を向上させることができます。 - コンバータの明示的指定 より複雑な制御が必要な場合は、
ValueConverterクラスを使用できます。C#var converter = new ValueConverter<TaskStatus, string>( v => v.ToString(), v => (TaskStatus)Enum.Parse(typeof(TaskStatus), v)); modelBuilder.Entity<ProjectTask>() .Property(e => e.Status) .HasConversion(converter);
注意点
- リネームによるデータ不整合 最も重要な注意点です。文字列として保存するため、C#コード側で
TaskStatus.InProgressをTaskStatus.Workingにリネームすると、データベース内の “InProgress” という文字列を Enum に変換できなくなり、読み込み時に例外が発生します。リネームにはデータ移行(UPDATE文での書き換え)が必要になります。 - パフォーマンスとストレージ 整数(int: 4バイト)と比較して、文字列はストレージ容量を多く消費します。また、検索やインデックス作成においても整数型の方が高速です。数千万件を超える大規模データの場合は注意が必要です。
- Typos(タイプミス) SQLで直接データを挿入・修正する場合、
InProgresのようにスペルミスをすると、アプリケーション側で読み込めなくなります。
バリエーション(任意)
属性(Attribute)で型を指定する場合
EF Core 5.0 以降でも、Enumの文字列変換を属性だけで完結させる標準機能はありませんが、カラムの型ヒントを与えることは可能です(変換処理自体はFluent APIで行う必要があります)。 以下のように書いても、HasConversion がなければ int として保存されようとして型不一致エラーになる場合があります。あくまでFluent APIが主役です。
[Column(TypeName = "nvarchar(24)")]
public TaskStatus Status { get; set; }
まとめ
Enumのメンバー名を変更すると過去のデータが読めなくなるリスクがあるため、名称変更には慎重な対応が必要です。
Enumを文字列保存するには、OnModelCreating で .HasConversion<string>() を設定します。
データベースの中身を見ただけで意味が分かるため、デバッグや運用保守が容易になります。
