EF Core の .NET イベント

ヒント

イベントのサンプルは GitHub からダウンロードできます。

Entity Framework Core (EF Core) では、EF Core コードで特定の処理が発生したときにコールバックとして機能する .NET イベントが公開されます。 イベントはインターセプターよりシンプルであり、より柔軟な登録が可能です。 ただし、これらは同期のみであるため、非ブロッキングの非同期 I/O を実行することはできません。

イベントは DbContext インスタンスごとに登録されます。 プロセス内のすべての DbContext インスタンスで同じ情報を取得するには、診断リスナーを使用します。

EF Core によって発生するイベント

次のイベントは、EF Core によって発生します。

イベント 発生するタイミング
DbContext.SavingChanges SaveChanges または SaveChangesAsync の開始時
DbContext.SavedChanges 成功した SaveChanges または SaveChangesAsync の最後
DbContext.SaveChangesFailed 失敗した SaveChanges または SaveChangesAsync の最後
ChangeTracker.Tracked エンティティがコンテキストによって追跡されるとき
ChangeTracker.StateChanged 追跡されるエンティティの状態が変更されたとき

例: タイムスタンプの状態の変更

DbContext によって追跡される各エンティティには EntityState があります。 たとえば、Added 状態は、エンティティがデータベースに挿入されることを示しています。

この例では TrackedStateChanged のイベントを使用して、エンティティの状態がいつ変更されたか検出します。 その後、現在の時刻でエンティティにスタンプが付けられ、この変更がいつ発生したのかを示します。 これにより、エンティティが挿入、削除、または最後に更新された日時を示すタイムスタンプが生成されます。

この例のエンティティ型は、タイムスタンプ プロパティを定義するインターフェイスを実装しています。

public interface IHasTimestamps
{
    DateTime? Added { get; set; }
    DateTime? Deleted { get; set; }
    DateTime? Modified { get; set; }
}

すると、アプリケーションの DbContext 上のメソッドでは、このインターフェイスを実装するすべてのエンティティのタイムスタンプを設定できます。

private static void UpdateTimestamps(object sender, EntityEntryEventArgs e)
{
    if (e.Entry.Entity is IHasTimestamps entityWithTimestamps)
    {
        switch (e.Entry.State)
        {
            case EntityState.Deleted:
                entityWithTimestamps.Deleted = DateTime.UtcNow;
                Console.WriteLine($"Stamped for delete: {e.Entry.Entity}");
                break;
            case EntityState.Modified:
                entityWithTimestamps.Modified = DateTime.UtcNow;
                Console.WriteLine($"Stamped for update: {e.Entry.Entity}");
                break;
            case EntityState.Added:
                entityWithTimestamps.Added = DateTime.UtcNow;
                Console.WriteLine($"Stamped for insert: {e.Entry.Entity}");
                break;
        }
    }
}

このメソッドには、TrackedStateChanged の両方のイベントのイベント ハンドラーとして使用する適切なシグネチャがあります。 ハンドラーは、DbContext コンストラクターの両方のイベントに対して登録されます。 イベントは DbContext にいつでもアタッチできることに注意してください。これはコンテキスト コンストラクターで発生する必要はありません。

public BlogsContext()
{
    ChangeTracker.StateChanged += UpdateTimestamps;
    ChangeTracker.Tracked += UpdateTimestamps;
}

新しいエンティティが最初に追跡された場合に Tracked イベントが発生するため、両方のイベントが必要です。 StateChanged イベントは、"既に" 追跡されているエンティティについて状態が変化した場合にのみ発生します。

この例のサンプルには、Blogging データベースに変更を加える単純なコンソール アプリケーションが含まれています。

using (var context = new BlogsContext())
{
    context.Database.EnsureDeleted();
    context.Database.EnsureCreated();

    context.Add(
        new Blog
        {
            Id = 1,
            Name = "EF Blog",
            Posts = { new Post { Id = 1, Title = "EF Core 3.1!" }, new Post { Id = 2, Title = "EF Core 5.0!" } }
        });

    context.SaveChanges();
}

using (var context = new BlogsContext())
{
    var blog = context.Blogs.Include(e => e.Posts).Single();

    blog.Name = "EF Core Blog";
    context.Remove(blog.Posts.First());
    blog.Posts.Add(new Post { Id = 3, Title = "EF Core 6.0!" });

    context.SaveChanges();
}

このコードからの出力は、状態の変更が発生し、タイムスタンプが適用されたことを示しています。

Stamped for insert: Blog 1 Added on: 10/15/2020 11:01:26 PM
Stamped for insert: Post 1 Added on: 10/15/2020 11:01:26 PM
Stamped for insert: Post 2 Added on: 10/15/2020 11:01:26 PM
Stamped for delete: Post 1 Added on: 10/15/2020 11:01:26 PM Deleted on: 10/15/2020 11:01:26 PM
Stamped for update: Blog 1 Added on: 10/15/2020 11:01:26 PM Modified on: 10/15/2020 11:01:26 PM
Stamped for insert: Post 3 Added on: 10/15/2020 11:01:26 PM