ひでぼ~blog

C#ときどきゲーム

EFCoreでChangeTrackerの内容を確認する

EFCoreのChangeTrackerについて今まで雰囲気で分かった気になっていたので、理解を深めるためにDbContextのデータをいろいろ操作しながらChangeTrackerの内容を確認してみます。

実行環境

Entityを用意する

次のようなEntityを作成します。

public record ToDo
{
    public required int Id { get; set; }

    public required string Description { get; set; }

    public required DateTime DueDate { get; set; }

    public required bool IsCompleted { get; set; }
}

ChangeTrackerの取得

ChangeTrackerは以下で確認できます。 ShortViewとLongViewの2つが用意されていて、ShortViewはシンプル、LongViewはより詳細な内容が確認できます。

dbContext.ChangeTracker.DebugView.ShortView
// or
dbContext.ChangeTracker.DebugView.LongView

LongViewはこのような感じになっています。

ToDo {Id: 1} Unchanged
  Id: 1 PK
  Description: 'コードレビュー'
  DueDate: '2023/06/29 0:00:00'
  IsCompleted: 'True'

CRUDしながらChangeTrackerの内容を出力する

何もしていない状態

dbContextに対してまだ何も操作を行っていない状態です。

var dbContext = new AppDbContext();
Console.WriteLine(dbContext.ChangeTracker.DebugView.LongView);

まだ何も追跡されていないので何も出力されません。




追加

var toDo = new ToDo { Id = 1, Description = "コードレビュー", DueDate = DateTime.Today, IsCompleted = true };

await dbContext.ToDos.AddAsync(toDo);
Console.WriteLine("AddAsync後");
Console.WriteLine(dbContext.ChangeTracker.DebugView.LongView);

await dbContext.SaveChangesAsync();
Console.WriteLine("SaveChangesAsync後");
Console.WriteLine(dbContext.ChangeTracker.DebugView.LongView);

ToDoの変更が追跡され、Addedとしてマークされました。また、SaveChangesAsyncすることでAddedからUnchangedに変わりました。

AddAsync後
ToDo {Id: 1} Added
  Id: 1 PK
  Description: 'コードレビュー'
  DueDate: '2023/06/29 0:00:00'
  IsCompleted: 'True'

SaveChangesAsync後
ToDo {Id: 1} Unchanged
  Id: 1 PK
  Description: 'コードレビュー'
  DueDate: '2023/06/29 0:00:00'
  IsCompleted: 'True'

読み取り

var toDo = await dbContext.ToDos.FirstAsync();
Console.WriteLine(dbContext.ChangeTracker.DebugView.LongView);

取得しただけの場合はUnchangedとしてマークされていました。

ToDo {Id: 1} Unchanged
  Id: 1 PK
  Description: 'コードレビュー'
  DueDate: '2023/06/29 0:00:00'
  IsCompleted: 'True'

更新

var toDo = await dbContext.ToDos.FirstAsync();
Console.WriteLine("FirstAsync後");
Console.WriteLine(dbContext.ChangeTracker.DebugView.LongView);

toDo.IsCompleted = false;
dbContext.ChangeTracker.DetectChanges();
Console.WriteLine("プロパティ変更後");
Console.WriteLine(dbContext.ChangeTracker.DebugView.LongView);

await dbContext.SaveChangesAsync();
Console.WriteLine("SaveChangesAsync後");
Console.WriteLine(dbContext.ChangeTracker.DebugView.LongView);

ToDoを取得し、IsCompletedをfalseにして更新してみました。 プロパティ変更後にModifiedとしてマークされています。また、IsCompletedというカラムがModifiedとなっているためカラムレベルで変更が追跡されていることが分かります。 こちらも追加のときと同様にSaveChangesAsyncするとUnchangedになりました。

FirstAsync後
ToDo {Id: 1} Unchanged
  Id: 1 PK
  Description: 'コードレビュー'
  DueDate: '2023/06/29 0:00:00'
  IsCompleted: 'True'

プロパティ変更後
ToDo {Id: 1} Modified
  Id: 1 PK
  Description: 'コードレビュー'
  DueDate: '2023/06/29 0:00:00'
  IsCompleted: 'False' Modified Originally 'True'

SaveChangesAsync後
ToDo {Id: 1} Unchanged
  Id: 1 PK
  Description: 'コードレビュー'
  DueDate: '2023/06/29 0:00:00'
  IsCompleted: 'False'

Modifiedに関してはLongViewを取得する前に

dbContext.ChangeTracker.DetectChanges();

を呼んでおかないとModifiedというマークがつかないというハマりポイントがありました。

削除

var toDo = await dbContext.ToDos.FirstAsync();
Console.WriteLine("FirstAsync後");
Console.WriteLine(dbContext.ChangeTracker.DebugView.LongView);

dbContext.ToDos.Remove(toDo);
Console.WriteLine("Remove後");
Console.WriteLine(dbContext.ChangeTracker.DebugView.LongView);

await dbContext.SaveChangesAsync();
Console.WriteLine("SaveChangesAsync後");
Console.WriteLine(dbContext.ChangeTracker.DebugView.LongView);

削除の場合はDELETEDというマークがつきました。 SaveChangesAsync後、ChangeTrackerの内容は空になりました。

FirstAsync後
ToDo {Id: 1} Unchanged
  Id: 1 PK
  Description: 'コードレビュー'
  DueDate: '2023/06/29 0:00:00'
  IsCompleted: 'True'

Remove後
ToDo {Id: 1} Deleted
  Id: 1 PK
  Description: 'コードレビュー'
  DueDate: '2023/06/29 0:00:00'
  IsCompleted: 'True'

SaveChangesAsync後

まとめ

ChangeTrackerはEntityに対してCRUDを行ったときに変更を追跡し、カラムレベルで追跡を行っていることが分かりました。 また、SaveChangesAsyncしたタイミングでそれまでに追跡していた操作をデータベースに対して実行することが分かりました。

参考

learn.microsoft.com

tsuna-can.hateblo.jp