ひでぼ~blog

C#ときどきゲーム

EFCoreのSaveChangesAsyncをoverrideして共通処理を差し込む

DBなどの永続化層のデータを更新する際に作成日や更新日などの日付を更新したいことがよくあります。そのあたりの処理を共通化してみたいと思います。

実行環境

  • VS 2022 17.6.5
  • .NET 7
  • EF Core 7.0.4

Entityを用意する

ITimeStampというインタフェースを持つEntityを定義します。

public class Todo : ITimeStamp
{
    public int Id { get; set; }

    public string? Content { get; set; }

    public DateTime Created { get; set; }

    public DateTime? Updated { get; set; }

    public DateTime? Deleted { get; set; }
}

public interface ITimeStamp
{
    DateTime Created { get; set; }

    DateTime? Updated { get; set; }

    DateTime? Deleted { get; set; }
}

SaveChangesをoverrideする

DbContextのSaveChanges(Async)をoverrideします。ChangeTrackerでEntityの状態(Added, Modified, Deleted)をチェックし、任意のカラムを更新するようにします。

public class AppDbContext : DbContext
{
    // ...

    public override int SaveChanges()
    {
        UpdateTimeStamp();
        return base.SaveChanges();
    }

    public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
    {
        UpdateTimeStamp();
        return base.SaveChangesAsync(cancellationToken);
    }

    private void UpdateTimeStamp()
    {
        foreach (var entry in ChangeTracker.Entries())
        {
            if (entry.Entity is not ITimeStamp)
            {
                return;
            }

            switch (entry.State)
            {
                case EntityState.Added:
                    entry.Property(nameof(ITimeStamp.Created)).CurrentValue = DateTime.Now;
                    break;

                case EntityState.Modified:
                    entry.Property(nameof(ITimeStamp.Updated)).CurrentValue = DateTime.Now;
                    break;

                case EntityState.Deleted:
                    // 論理削除にするためModifiedにする
                    entry.State = EntityState.Modified;
                    entry.Property(nameof(ITimeStamp.Deleted)).CurrentValue = DateTime.Now;
                    break;

            }
        }
    }
}

データを投入してみる

Todoを3つ追加してみます。

var dbContext = new AppDbContext();

var todos = new List<Todo>
{
    new Todo { Id = 1, Content = "サーモンラン" },
    new Todo { Id = 2, Content = "ガチホコ" },
    new Todo { Id = 3, Content = "ガチヤグラ" },
};

await dbContext.Todos.AddRangeAsync(todos);
await dbContext.SaveChangesAsync();

Createdに現在時刻がセットされました。

データを更新してみる

Todoを1件更新してみます。

var todo = await dbContext.Todos.FirstAsync();
todo.Content = "ビッグラン";

await dbContext.SaveChangesAsync();

Updatedが更新されました。

データを削除してみる

Todoを論理削除してみます。

var todo = await dbContext.Todos.FirstAsync();
dbContext.Todos.Remove(todo);

await dbContext.SaveChangesAsync();

Deletedが更新されました。

参考

qiita.com