Skip to content

PostgreSQLとDateTime Kind=UTCの問題:timestamp without time zoneへの書き込みエラーの解決

問題概要

.NET 6アプリケーションでPostgreSQLを使用する際、次のエラーが発生することがあります:

Cannot write DateTime with Kind=UTC to PostgreSQL type 'timestamp without time zone'

このエラーの原因は、Npgsqlプロバイダー(バージョン6.0以降)の動作変更にあります。以前のバージョンではUTC時刻をtimestamp without time zoneに自動変換できましたが、新しいバージョンでは明示的な処理が必要になりました。

解決方法

方法1: レガシー動作の有効化(推奨)

Npgsqlのレガシー動作を有効にすることで、問題を解決できます。

csharp
var builder = WebApplication.CreateBuilder(args);

// サービス設定など...

var host = builder.Build();
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
// その他の設定
await host.RunAsync();
csharp
public class MyDbContext : DbContext
{
    static MyDbContext()
    {
        AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
    }
    
    public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { }
}
csharp
using System.Runtime.CompilerServices;

namespace Your.Namespace;

public static class MyModuleInitializer
{
    [ModuleInitializer]
    public static void Initialize()
    {
        AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
    }
}

TIP

EFマイグレーションを使用する場合、DbContextコンストラクタでもスイッチを設定する必要があります。これを行わないと、マイグレーション実行時にタイムゾーン関連のフィールド変更が試みられる可能性があります。

方法2: データ型の明示的設定

Entity Frameworkの設定でDateTimeの型を明示的に指定します。

csharp
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    base.ConfigureConventions(configurationBuilder);
    
    configurationBuilder.Properties<DateTime>()
        .HaveColumnType("timestamp without time zone");
    
    configurationBuilder.Properties<DateTime?>()
        .HaveColumnType("timestamp without time zone");
}

方法3: DateTimeKindの明示的設定

すべてのDateTime値のKindを明示的に設定する拡張メソッドを使用します。

csharp
public static class DateTimeExtensions
{
    public static DateTime? SetKindUtc(this DateTime? dateTime)
    {
        return dateTime.HasValue ? dateTime.Value.SetKindUtc() : null;
    }
    
    public static DateTime SetKindUtc(this DateTime dateTime)
    {
        return dateTime.Kind == DateTimeKind.Utc 
            ? dateTime 
            : DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
    }
}
csharp
// 作成時
var entity = new MyEntity 
{
    CreatedAt = DateTime.Now.SetKindUtc(),
    UpdatedAt = DateTime.UtcNow // こちらでも可
};

// 更新時
entity.UpdatedAt = DateTime.Now.SetKindUtc();

方法4: カスタムValueConverterの使用

Entity FrameworkのValueConverterを使用して自動変換を実装します。

csharp
public class DateTimeToDateTimeUtc : ValueConverter<DateTime, DateTime>
{
    public DateTimeToDateTimeUtc() 
        : base(c => DateTime.SpecifyKind(c, DateTimeKind.Utc), c => c)
    {
    }
}

// DbContextで設定
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.Properties<DateTime>()
        .HaveConversion(typeof(DateTimeToDateTimeUtc));
}

ベストプラクティス

データベース設計の見直し

長期的な解決策として、データベーススキーマをtimestamp with time zone型を使用するように変更することを検討してください。

sql
-- 既存のカラムの変更
ALTER TABLE your_table 
ALTER COLUMN your_column TYPE TIMESTAMP WITH TIME ZONE;

-- 新しいテーブルの作成
CREATE TABLE example (
    id SERIAL PRIMARY KEY,
    event_time TIMESTAMP WITH TIME ZONE NOT NULL
);

アプリケーション全体での一貫性の確保

  • すべてのDateTime値に対してDateTime.UtcNowを使用する
  • ユーザー入力は即時にUTCに変換する
  • フロントエンドとのデータ交換では明確なタイムゾーン情報を含める

トラブルシューティング

よくあるミス

  1. DateTime.Nowの使用

    csharp
    // 誤り
    var date = DateTime.Now;
    
    // 正しい
    var date = DateTime.UtcNow;
  2. 変換の抜け漏れ

    • APIからの受信データ
    • ユーザー入力
    • 外部システム連携
  3. マイグレーション時の設定忘れ

    • DbContextコンストラクタでのスイッチ設定が必要

デバッグ方法

csharp
// DateTimeのKindを確認
Console.WriteLine($"DateTimeKind: {myDateTime.Kind}");

// すべてのプロパティをチェックするメソッド
public void CheckDateTimeProperties(object entity)
{
    foreach (var prop in entity.GetType().GetProperties())
    {
        if (prop.PropertyType == typeof(DateTime))
        {
            var value = (DateTime)prop.GetValue(entity);
            Console.WriteLine($"{prop.Name}: {value} (Kind: {value.Kind})");
        }
    }
}

まとめ

PostgreSQLのtimestamp without time zoneとUTC DateTimeの問題は、以下のいずれかの方法で解決できます:

  1. レガシー動作の有効化 - 簡単だが将来の互換性に注意
  2. 明示的な型設定 - EFの設定で解決
  3. DateTimeKindの明示的設定 - 確実だが手動での対応が必要
  4. データベース設計の変更 - 長期的には最も健全な解決策

アプリケーションの規模や要件に応じて最適な方法を選択してください。新規プロジェクトでは、最初からtimestamp with time zoneの使用を検討することをお勧めします。