Dela via


Att använda diagnostiklyssnare i EF Core

Tips/Råd

Du kan ladda ned den här artikelns exempel från GitHub.

Diagnostiklyssnare tillåter att du lyssnar efter alla EF Core-händelser som inträffar i den aktuella .NET-processen. Klassen DiagnosticListener är en del av en vanlig mekanism i .NET för att hämta diagnostikinformation från program som körs.

Diagnostiklyssnare är inte lämpliga för att hämta händelser från en enda DbContext-instans. EF Core-avlyssningsappar ger åtkomst till samma händelser med registrering per kontext.

Diagnostiklyssnare är inte utformade för loggning. Överväg att använda enkel loggning eller Microsoft.Extensions.Logging för loggning.

Exempel: Observera diagnostikhändelser

Att lösa EF Core-händelser är en tvåstegsprocess. För det första måste en observatör för DiagnosticListener sig själv skapas:

public class DiagnosticObserver : IObserver<DiagnosticListener>
{
    public void OnCompleted()
        => throw new NotImplementedException();

    public void OnError(Exception error)
        => throw new NotImplementedException();

    public void OnNext(DiagnosticListener value)
    {
        if (value.Name == DbLoggerCategory.Name) // "Microsoft.EntityFrameworkCore"
        {
            value.Subscribe(new KeyValueObserver());
        }
    }
}

Metoden OnNext söker efter DiagnosticListener som kommer från EF Core. Den här lyssnaren har namnet "Microsoft.EntityFrameworkCore", som kan hämtas från DbLoggerCategory klassen enligt bilden.

Den här övervakaren måste sedan registreras globalt, till exempel i programmets Main metod:

DiagnosticListener.AllListeners.Subscribe(new DiagnosticObserver());

För det andra skapas en ny nyckelvärdesobservatör när EF Core DiagnosticListener hittas för att prenumerera på de faktiska EF Core-händelserna. Till exempel:

public class KeyValueObserver : IObserver<KeyValuePair<string, object>>
{
    public void OnCompleted()
        => throw new NotImplementedException();

    public void OnError(Exception error)
        => throw new NotImplementedException();

    public void OnNext(KeyValuePair<string, object> value)
    {
        if (value.Key == CoreEventId.ContextInitialized.Name)
        {
            var payload = (ContextInitializedEventData)value.Value;
            Console.WriteLine($"EF is initializing {payload.Context.GetType().Name} ");
        }

        if (value.Key == RelationalEventId.ConnectionOpening.Name)
        {
            var payload = (ConnectionEventData)value.Value;
            Console.WriteLine($"EF is opening a connection to {payload.Connection.ConnectionString} ");
        }
    }
}

Metoden OnNext anropas den här gången med ett nyckel/värde-par för varje EF Core-händelse. Nyckeln är namnet på händelsen, som kan hämtas från något av:

  • CoreEventId för händelser som är gemensamma för alla EF Core-databasprovidrar
  • RelationalEventId för händelser som är gemensamma för alla relationsdatabasprovidrar
  • En liknande klass för händelser som är specifika för den aktuella databasprovidern. Till exempel SqlServerEventId för SQL Server-providern.

Värdet för nyckel/värde-paret är en typ av nyttolast specifik för händelsen. Den typ av nyttolast som ska förväntas dokumenteras för varje händelse som definieras i dessa händelseklasser.

Koden ovan hanterar till exempel ContextInitialized- och ConnectionOpening-händelserna. För den första av dessa är nyttolasten ContextInitializedEventData. För det andra är ConnectionEventData.

Tips/Råd

I varje EF Core-händelsedataklass skrivs ToString över för att generera motsvarande loggmeddelande för händelsen. Anrop ContextInitializedEventData.ToString genererar till exempel "Entity Framework Core 5.0.0 initierade "BlogsContext" med providern "Microsoft.EntityFrameworkCore.Sqlite" med alternativ: Ingen".

Exemplet innehåller ett enkelt konsolprogram som gör ändringar i bloggdatabasen och skriver ut de diagnostikhändelser som påträffas.

public static async Task Main()
{
    DiagnosticListener.AllListeners.Subscribe(new DiagnosticObserver());

    using (var context = new BlogsContext())
    {
        await context.Database.EnsureDeletedAsync();
        await context.Database.EnsureCreatedAsync();

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

        await context.SaveChangesAsync();
    }

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

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

        await context.SaveChangesAsync();
    }

Utdata från den här koden visar de händelser som identifierats:

EF is initializing BlogsContext
EF is opening a connection to Data Source=blogs.db;Mode=ReadOnly
EF is opening a connection to DataSource=blogs.db
EF is opening a connection to Data Source=blogs.db;Mode=ReadOnly
EF is opening a connection to DataSource=blogs.db
EF is opening a connection to DataSource=blogs.db
EF is opening a connection to DataSource=blogs.db
EF is initializing BlogsContext
EF is opening a connection to DataSource=blogs.db
EF is opening a connection to DataSource=blogs.db