【C#】Entity Framework Coreでカラムにインデックスを作成して検索を高速化する方法

目次

概要

データベースのパフォーマンスチューニングにおいて、適切なインデックスの作成は不可欠です。 Entity Framework Core (EF Core) では、Fluent API を使用することで、特定のカラムに対してインデックスを作成したり、ユニーク制約(重複禁止)を付与したりすることができます。 本記事では、モデル構築時にインデックス定義を追加し、検索速度の向上やデータの整合性を保つ実装方法を解説します。

仕様(入出力)

  • 入力: エンティティクラスの特定プロパティ。
  • 処理: DbContextOnModelCreating メソッド内で HasIndex を呼び出し設定を行う。
  • 出力: データベース生成時(またはマイグレーション適用時)に、指定したカラムに対するインデックスが作成される。

基本の使い方

DbContext クラスの OnModelCreating メソッドをオーバーライドし、対象エンティティに対して HasIndex メソッドを使用します。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Product>()
        .HasIndex(p => p.Barcode) // インデックスを作成
        .IsUnique();              // 重複を許可しない(ユニーク制約)
}

コード全文

以下のコードは、社員情報の「メールアドレス」にユニークインデックスを、勤怠ログの「打刻時間」に名前付きインデックスを設定する完全なコンソールアプリケーションの例です。 EnsureCreated() 実行時に、SQLiteデータベース上にこれらのインデックスが反映されます。

※ 実行には Microsoft.EntityFrameworkCore.Sqlite パッケージが必要です。 dotnet add package Microsoft.EntityFrameworkCore.Sqlite

using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;

namespace IndexSample
{
    // 1. エンティティクラスの定義
    public class Employee
    {
        public int Id { get; set; }
        public string Name { get; set; } = string.Empty;
        public string Email { get; set; } = string.Empty; // ユニーク制約対象
    }

    public class AttendanceLog
    {
        public int Id { get; set; }
        public int EmployeeId { get; set; }
        public DateTime PunchTime { get; set; } // 検索用インデックス対象
        public string Note { get; set; } = string.Empty;
    }

    // 2. DbContextの定義
    public class CompanyContext : DbContext
    {
        public DbSet<Employee> Employees { get; set; }
        public DbSet<AttendanceLog> Logs { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            // 動作確認用にSQLiteファイルを使用
            optionsBuilder.UseSqlite("Data Source=company.db");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            // Employeeエンティティの設定
            modelBuilder.Entity<Employee>()
                .HasIndex(e => e.Email) // Emailカラムにインデックスを作成
                .IsUnique();            // ユニーク制約(重複登録でエラーになる)

            // AttendanceLogエンティティの設定
            modelBuilder.Entity<AttendanceLog>()
                .HasIndex(l => l.PunchTime)               // PunchTimeカラムにインデックスを作成
                .HasDatabaseName("IX_Attendance_Time");   // インデックス名を明示的に指定
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            using (var context = new CompanyContext())
            {
                // データベースの再作成(スキーマ変更を反映するため)
                context.Database.EnsureDeleted();
                context.Database.EnsureCreated();
                Console.WriteLine("データベースとインデックスを作成しました。");

                try
                {
                    // 正常データの登録
                    var emp1 = new Employee { Name = "Alice", Email = "alice@example.com" };
                    var emp2 = new Employee { Name = "Bob", Email = "bob@example.com" };
                    context.Employees.AddRange(emp1, emp2);
                    
                    // ログデータの登録
                    context.Logs.Add(new AttendanceLog 
                    { 
                        EmployeeId = 1, 
                        PunchTime = DateTime.Now, 
                        Note = "Arrived" 
                    });

                    context.SaveChanges();
                    Console.WriteLine("初期データを保存しました。");

                    // ユニーク制約違反のテスト
                    Console.WriteLine("重複するメールアドレスを登録してみます...");
                    var duplicateEmp = new Employee { Name = "Alice2", Email = "alice@example.com" };
                    context.Employees.Add(duplicateEmp);
                    context.SaveChanges(); // ここで例外が発生します
                }
                catch (DbUpdateException ex)
                {
                    Console.WriteLine($"[想定通りのエラー] データ保存に失敗しました: {ex.InnerException?.Message}");
                    Console.WriteLine("ユニークインデックスにより重複データが防がれました。");
                }
            }
        }
    }
}

実行結果例

データベースとインデックスを作成しました。
初期データを保存しました。
重複するメールアドレスを登録してみます...
[想定通りのエラー] データ保存に失敗しました: SQLite Error 19: 'UNIQUE constraint failed: Employees.Email'.
ユニークインデックスにより重複データが防がれました。

カスタムポイント

  • 複合インデックス(Composite Index) 複数のカラムを組み合わせた検索を高速化したい場合に使用します。C#modelBuilder.Entity<User>() .HasIndex(u => new { u.FirstName, u.LastName });
  • インデックスフィルタ(Filtered Index) 特定の条件を満たす行にのみインデックスを作成し、サイズを削減・効率化します(SQL Serverなど対応DBのみ)。C#modelBuilder.Entity<Product>() .HasIndex(p => p.Url) .HasFilter("[Url] IS NOT NULL");

注意点

  1. 書き込み性能への影響 インデックスを増やすと読み取り(SELECT)は速くなりますが、書き込み(INSERT/UPDATE/DELETE)のたびにインデックス更新が必要になるため、処理速度が低下します。不要なインデックスは作成しないでください。
  2. 既存データとの整合性 既に重複データが存在するカラムに対して後から .IsUnique() を設定すると、マイグレーション適用時にエラーとなります。事前にデータのクレンジングが必要です。
  3. データベースエンジンの制限 インデックスキーの最大長やサポートされる型はデータベース製品(SQL Server, MySQL, PostgreSQL, SQLite)ごとに異なります。長い文字列にインデックスを貼る際は注意が必要です。

バリエーション(任意)

属性(Attribute)を使用した定義

EF Core 5.0 以降では、エンティティクラスに直接 [Index] 属性を付与して定義することも可能です。コードが分散せず、シンプルに記述できます。

using Microsoft.EntityFrameworkCore;

// クラス定義時にインデックスを指定
[Index(nameof(Email), IsUnique = true)]
[Index(nameof(Username))]
public class Account
{
    public int Id { get; set; }
    
    public string Username { get; set; }
    
    public string Email { get; set; }
}

まとめ

  • 検索頻度の高いカラムや、並べ替えに使用するカラムには HasIndex でインデックスを作成します。
  • データの重複を防ぎたい場合は .IsUnique() を追加してユニーク制約を設けます。
  • Fluent API (OnModelCreating) または データ注釈属性 ([Index]) のいずれかで実装可能です。
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

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

目次