【C#】Entity Framework Coreで列挙型(Enum)を文字列としてデータベースに保存する方法

目次

概要

Entity Framework Core (EF Core) では、列挙型(Enum)のプロパティはデフォルトで整数(int)としてデータベースに保存されます。 しかし、データベースを直接参照した際の可読性を高めたり、既存の文字列カラムを持つデータベースと連携したりするために、Enumの「名前(文字列)」で保存したい場合があります。 本記事では、Fluent APIの HasConversion メソッドを使用して、Enumを文字列としてマッピングする方法を解説します。

仕様(入出力)

  • 入力: Enum型プロパティを持つエンティティ。
  • 処理: HasConversion<string>() を適用し、値の変換ルールを定義する。
  • 出力: データベースのカラム型がテキスト(TEXT, VARCHAR等)になり、Enumのメンバー名(”Active”, “Deleted” 等)が保存される。

基本の使い方

DbContextOnModelCreating メソッド内で、対象のプロパティに対して変換設定を行います。

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);

注意点

  1. リネームによるデータ不整合 最も重要な注意点です。文字列として保存するため、C#コード側で TaskStatus.InProgressTaskStatus.Workingリネームすると、データベース内の “InProgress” という文字列を Enum に変換できなくなり、読み込み時に例外が発生します。リネームにはデータ移行(UPDATE文での書き換え)が必要になります。
  2. パフォーマンスとストレージ 整数(int: 4バイト)と比較して、文字列はストレージ容量を多く消費します。また、検索やインデックス作成においても整数型の方が高速です。数千万件を超える大規模データの場合は注意が必要です。
  3. 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>() を設定します。

データベースの中身を見ただけで意味が分かるため、デバッグや運用保守が容易になります。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次