Eventos do .NET no EF Core

Dica

Você pode baixar as amostras de eventos do GitHub.

O EF Core (Entity Framework Core) expõe eventos do .NET para agir como retornos de chamada quando determinadas coisas acontecem no código do EF Core. Os eventos são mais simples que os interceptadores e permitem um registro mais flexível. No entanto, eles são apenas sincronizados e, portanto, não podem executar E/S assíncrona sem bloqueio.

Os eventos são registrados por instância DbContext. Use um ouvinte de diagnóstico para obter as mesmas informações, mas para todas as instâncias de DbContext no processo.

Eventos gerados pelo EF Core

Os seguintes eventos são gerados pelo EF Core:

Evento Quando gerado
DbContext.SavingChanges No início de SaveChanges ou SaveChangesAsync
DbContext.SavedChanges No final de um SaveChanges ou SaveChangesAsyncbem-sucedido
DbContext.SaveChangesFailed No final de um SaveChanges ou SaveChangesAsynccom falha
ChangeTracker.Tracked Quando uma entidade é rastreada pelo contexto
ChangeTracker.StateChanged Quando uma entidade rastreada altera seu estado

Exemplo: alterações no estado do carimbo de data/hora

Cada entidade rastreada por um DbContext tem um EntityState. Por exemplo, o estado Added indica que a entidade será inserida no banco de dados.

Este exemplo usa os eventos Tracked e StateChanged para detectar quando uma entidade altera o estado. Em seguida, ele carimba a entidade com a hora atual indicando quando essa alteração aconteceu. Isso resulta em carimbos de data/hora que indicam quando a entidade foi inserida, excluída e/ou atualizada pela última vez.

Os tipos de entidade neste exemplo implementam uma interface que define as propriedades do carimbo de data/hora:

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

Um método no DbContext do aplicativo pode definir carimbos de data/hora para qualquer entidade que implemente essa interface:

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;
        }
    }
}

Esse método tem a assinatura apropriada a ser usada como um manipulador de eventos para os eventos Tracked e StateChanged. O manipulador é registrado para ambos os eventos no construtor DbContext. Observe que os eventos podem ser anexados a um DbContext a qualquer momento; não é necessário que isso ocorra no construtor de contexto.

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

Ambos os eventos são necessários porque novas entidades disparam eventos Tracked quando são rastreados pela primeira vez. Eventos StateChanged são disparados apenas para entidades que alteram o estado enquanto estão sendo rastreadas.

A amostra deste exemplo contém um aplicativo de console simples que faz alterações no banco de dados de blog:

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();
}

A saída desse código mostra as alterações de estado acontecendo e os carimbos de data/hora sendo aplicados:

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