Eventos de .NET en EF Core

Sugerencia

Puede descargar el ejemplo de eventos de GitHub.

Entity Framework Core (EF Core) expone eventos de .NET para que actúen como devoluciones de llamada cuando ocurran ciertas cosas en el código EF Core. Los eventos son más sencillos que los interceptores y permiten un registro más flexible. Sin embargo, solo son sincrónicos y, por tanto, no pueden realizar operaciones de E/S asincrónicas sin bloqueo.

Los eventos se registran por instancia de DbContext. Use una escucha de diagnóstico para obtener la misma información, pero para todas las instancias de DbContext del proceso.

Eventos generados por EF Core

EF Core genera los siguientes eventos:

Evento Cuando se genera
DbContext.SavingChanges Al principio de SaveChanges o SaveChangesAsync
DbContext.SavedChanges Al final de una operación correcta SaveChanges o SaveChangesAsync
DbContext.SaveChangesFailed Al final de una SaveChanges o SaveChangesAsync con errores
ChangeTracker.Tracked Cuando el contexto realiza un seguimiento de una entidad
ChangeTracker.StateChanged Cuando una entidad con seguimiento cambia su estado

Ejemplo: cambios de estado de marca de tiempo

Cada entidad de la que realiza un seguimiento DbContext tiene un EntityState. Por ejemplo, el estado Added indica que la entidad se insertará en la base de datos.

En este ejemplo se usan los eventos Tracked y StateChanged para detectar cuándo cambia el estado de una entidad. A continuación, marca la entidad con la hora actual que indica cuándo se produjo este cambio. Esto da como resultado marcas de tiempo que indican cuándo se insertó, eliminó o actualizó por última vez la entidad.

Los tipos de entidad de este ejemplo implementan una interfaz que define las propiedades de marca de tiempo:

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

A continuación, un método de DbContext de la aplicación puede establecer marcas de tiempo para cualquier entidad que implemente esta interfaz:

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

Este método tiene la firma adecuada para usar como controlador de eventos para los eventos Tracked y StateChanged. El controlador se registra para ambos eventos en el constructor DbContext. Tenga en cuenta que los eventos se pueden adjuntar a DbContext en cualquier momento; no es necesario que esto suceda en el constructor de contexto.

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

Ambos eventos son necesarios porque las nuevas entidades activan eventos Tracked cuando se realiza el seguimiento por primera vez. Los eventos StateChanged solo se activan para las entidades que cambian de estado cuando ya se están supervisando.

El ejemplo de este ejemplo contiene una aplicación de consola sencilla que realiza cambios en la base de datos de registro:

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

La salida de este código muestra los cambios de estado que se producen y las marcas de tiempo que se aplican:

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