しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

Entity Framework 6 で登録日と更新日を自動設定する

Entity Framework 6 でも LightSpeed みたいに CreatedOn / UpdatedOn というプロパティがあれば、自動的に登録日と更新日を設定してほしい人生でした。

なので人形町の夜王こと nabehiro 先生のブログのコードをパクろうかと思いましたが、リフレクションが気に食わなかったので、そのあたりだけ変えました。

基本的な方針は元コードと変わらず、ChangeTracker が追跡中のエンティティから追加・変更されたものだけを取得し、CreatedOn / UpdatedOn プロパティに値を設定しています。

非同期版だけオーバーライドしてますが、必要によっては同期版を弄る必要があります。

public override Task<int> SaveChangesAsync(CancellationToken cancellationToken)
{
    var utcNow = DateTime.UtcNow;

    foreach (var entry in ChangeTracker.Entries().Where(x => x.State == EntityState.Added || x.State == EntityState.Modified))
    {
        var createdOn = entry.SafeGetProperty("CreatedOn");
        var updatedOn = entry.SafeGetProperty("UpdatedOn");

        if (entry.State == EntityState.Added && createdOn != null)
        {
            createdOn.CurrentValue = utcNow;
        }

        if (updatedOn != null)
        {
            updatedOn.CurrentValue = utcNow;
        }
    }

    return base.SaveChangesAsync(cancellationToken);
}

夜王先生は Entity Type からリフレクションでプロパティの存在チェックとキャッシュを行っていましたが、DbEntityEntry にはプロパティ名を持っているのでそれを使います。

おまけ的にプロパティの存在チェックをしてくれる拡張メソッドを用意しました。存在しない場合には null を返すだけのとても簡単なやつです。

internal static class DbEntityExtension
{
    public static DbPropertyEntry SafeGetProperty(this DbEntityEntry entry, string propertyName)
    {
        if (entry.CurrentValues.PropertyNames.Contains(propertyName))
        {
            return entry.Property(propertyName);
        }

        return null;
    }
}

後は普通にエンティティを追加、更新すれば SaveChangesAsync を呼び出したタイミングで日付が自動的に設定されます。当然ながら CreatedOn / UpdatedOn プロパティが存在しない場合には何もしません。

f:id:shiba-yan:20150321232602p:plain

最初はインターセプターで解決したいと思っていましたが、どうも今のインターセプターの仕様だとロギングぐらいにしか使え無さそうです。ちょっと残念な感じ。