【C#】EF Coreでコマンドタイムアウト値を設定する方法

目次

概要

Entity Framework Core (EF Core) がデータベースに対してSQLを実行する際の待機時間(タイムアウト)を設定する方法です。 デフォルト(通常30秒)よりも長い時間がかかる集計処理や一括更新処理を行う場合に、意図的な切断(TimeoutException)を防ぐために設定値を延長します。

仕様(入出力)

  • 入力: タイムアウト秒数(int)。
  • 出力: 設定が適用された DbContext インスタンス。
  • 前提: .NET 6.0以上。
    • リレーショナルデータベースプロバイダ(SQLite, SQL Server, PostgreSQL等)で有効です。
    • 本コードは Microsoft.EntityFrameworkCore.Sqlite を使用します。

基本の使い方

DbContextOptionsBuilder のプロバイダ設定用メソッド(UseSqlite, UseSqlServer 等)の第2引数で、アクションデリゲートを使用して設定します。

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.UseSqlite("Data Source=app.db", options =>
    {
        // タイムアウトを 180秒(3分)に設定
        options.CommandTimeout(180);
    });
}

コード全文

ここではコンソールアプリケーションで DbContext を初期化する際に、タイムアウト値を構成する完全な例を提示します。 DIコンテナ(AddDbContext)を使用する場合も、設定記述の場所が Program.cs に変わるだけで記法は同じです。

外部ライブラリとして Microsoft.EntityFrameworkCore.Sqlite が必要です。

dotnet add package Microsoft.EntityFrameworkCore.Sqlite
using System;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;

public class Program
{
    public static async Task Main()
    {
        // タイムアウト設定が適用されたContextを使用
        using (var context = new LongRunningContext())
        {
            // DB作成
            await context.Database.EnsureDeletedAsync();
            await context.Database.EnsureCreatedAsync();

            Console.WriteLine($"現在のタイムアウト値: {context.Database.GetCommandTimeout()} 秒");

            // 通常通りデータ操作を実行
            context.Products.Add(new Product { Name = "Heavy Calculation Item" });
            await context.SaveChangesAsync();
            
            Console.WriteLine("データベース操作が完了しました。");
        }
    }
}

// DbContext定義
public class LongRunningContext : DbContext
{
    // コンストラクタでオプションを受け取るパターン(DI使用時など)
    public LongRunningContext() { }
    public LongRunningContext(DbContextOptions<LongRunningContext> options) : base(options) { }

    public DbSet<Product> Products => Set<Product>();

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        // 既に設定されていない場合のみ設定(DIコンテナでの設定を優先するため)
        if (!optionsBuilder.IsConfigured)
        {
            // 接続文字列
            var connectionString = "Data Source=app.db";

            optionsBuilder.UseSqlite(connectionString, sqliteOptions =>
            {
                // 【ここがポイント】
                // コマンド実行のタイムアウト時間を 180秒 (3分) に設定
                // デフォルトは通常 30秒
                sqliteOptions.CommandTimeout(180);
            });
            
            // ログ出力(動作確認用)
            optionsBuilder.LogTo(Console.WriteLine, Microsoft.Extensions.Logging.LogLevel.Information);
        }
    }
}

// エンティティ定義
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
}

カスタムポイント

  • DIコンテナでの設定(ASP.NET Core等): Program.csAddDbContext を使用する場合は以下のように記述します。C#builder.Services.AddDbContext<LongRunningContext>(options => options.UseSqlite( connectionString, providerOptions => providerOptions.CommandTimeout(180) ) );
  • 一時的な変更: アプリケーション全体ではなく、特定の重い処理の間だけタイムアウトを延ばしたい場合は、コード内で動的に変更可能です(応用を参照)。

注意点

  1. デフォルト値: 多くのプロバイダではデフォルトが30秒です。設定しない場合、30秒を超えるクエリは OperationCanceledExceptionSqlException で失敗します。
  2. 長すぎる設定: 無闇に長い時間(または無限 0)を設定すると、データベースのロックが長時間解放されず、他のユーザーのアクセスを阻害する原因になります。必要な処理にのみ適用してください。
  3. 接続タイムアウトとの違い: CommandTimeout は「クエリの実行待ち時間」です。「DBへの接続待ち時間」である Connect Timeout(接続文字列で指定)とは別物ですので混同しないようにしてください。

応用

特定の処理だけタイムアウトを変更する

Context全体の設定を変えるのではなく、特定の重いレポート出力処理の時だけ時間を延ばしたい場合の方法です。

using (var context = new LongRunningContext())
{
    // このコンテキストインスタンスのタイムアウトを一時的に5分に変更
    context.Database.SetCommandTimeout(300);

    // 時間のかかる集計処理を実行
    // await context.Database.ExecuteSqlRawAsync("EXEC VeryHeavyStoredProcedure");
    
    // 処理が終わったら戻すことも可能(またはContextを破棄)
    context.Database.SetCommandTimeout(null); // nullでデフォルトに戻る
}

まとめ

バッチ処理や大量データのマイグレーション時など、明確に時間がかかると分かっている場合に有効です。

UseSqliteUseSqlServer のオプション用ラムダ式内で CommandTimeout を指定します。

全体設定は OnConfiguring または AddDbContext で行い、局所的な設定は Database.SetCommandTimeout を使用します。

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

この記事を書いた人

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

目次