Eventi .NET in EF Core

Suggerimento

È possibile scaricare l'esempio di eventi da GitHub.

Entity Framework Core (EF Core) espone gli eventi .NET per agire come callback quando si verificano determinati elementi nel codice EF Core. Gli eventi sono più semplici degli intercettori e consentono una registrazione più flessibile. Tuttavia, sono solo sincronizzati e quindi non possono eseguire operazioni di I/O asincrone non bloccabili.

Gli eventi vengono registrati per ogni DbContext istanza. Usare un listener di diagnostica per ottenere le stesse informazioni, ma per tutte le istanze di DbContext nel processo.

Eventi generati da EF Core

Gli eventi seguenti vengono generati da EF Core:

Event Quando generato
DbContext.SavingChanges All'inizio di SaveChanges o SaveChangesAsync
DbContext.SavedChanges Alla fine di un esito positivo SaveChanges o SaveChangesAsync
DbContext.SaveChangesFailed Alla fine di un errore SaveChanges o SaveChangesAsync
ChangeTracker.Tracked Quando un'entità viene rilevata dal contesto
ChangeTracker.StateChanged Quando un'entità rilevata cambia lo stato

Esempio: Modifiche dello stato del timestamp

Ogni entità rilevata da un oggetto DbContext ha un oggetto EntityState. Ad esempio, lo Added stato indica che l'entità verrà inserita nel database.

In questo esempio vengono usati gli Tracked eventi e StateChanged per rilevare quando un'entità cambia stato. Contrassegna quindi l'entità con l'ora corrente che indica quando si è verificata questa modifica. In questo modo si ottengono timestamp che indicano quando l'entità è stata inserita, eliminata e/o aggiornata per ultimo.

I tipi di entità in questo esempio implementano un'interfaccia che definisce le proprietà del timestamp:

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

Un metodo nel DbContext dell'applicazione può quindi impostare timestamp per qualsiasi entità che implementa questa interfaccia:

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

Questo metodo ha la firma appropriata da usare come gestore eventi per entrambi gli Tracked eventi e StateChanged . Il gestore viene registrato per entrambi gli eventi nel costruttore DbContext. Si noti che gli eventi possono essere collegati a un Oggetto DbContext in qualsiasi momento; non è necessario che ciò si verifichi nel costruttore di contesto.

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

Entrambi gli eventi sono necessari perché le nuove entità generano Tracked eventi quando vengono rilevati per la prima volta. StateChanged gli eventi vengono generati solo per le entità che modificano lo stato mentre sono già tracciate.

L'esempio per questo esempio contiene una semplice applicazione console che apporta modifiche al database di 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();
}

L'output di questo codice mostra le modifiche apportate allo stato e i timestamp applicati:

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