Używanie odbiorników diagnostycznych w programie EF Core

Napiwek

Przykład tego artykułu można pobrać z usługi GitHub.

Odbiorniki diagnostyczne umożliwiają nasłuchiwanie dowolnego zdarzenia programu EF Core, które występuje w bieżącym procesie platformy .NET. Klasa DiagnosticListener jest częścią wspólnego mechanizmu na platformie .NET do uzyskiwania informacji diagnostycznych z uruchomionych aplikacji.

Odbiorniki diagnostyczne nie są odpowiednie do pobierania zdarzeń z pojedynczego wystąpienia obiektu DbContext. Przechwytniki platformy EF Core zapewniają dostęp do tych samych zdarzeń z rejestracją poszczególnych kontekstów.

Odbiorniki diagnostyczne nie są przeznaczone do rejestrowania. Rozważ użycie prostego rejestrowania lub Microsoft.Extensions.Logging na potrzeby rejestrowania.

Przykład: obserwowanie zdarzeń diagnostycznych

Rozwiązywanie zdarzeń platformy EF Core jest procesem dwuetapowym. Najpierw należy utworzyć obserwatora DiagnosticListener:

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

Metoda OnNext szuka elementu DiagnosticListener pochodzącego z platformy EF Core. Ten odbiornik ma nazwę "Microsoft.EntityFrameworkCore", którą można uzyskać z DbLoggerCategory klasy, jak pokazano.

Ten obserwator musi być następnie zarejestrowany globalnie, na przykład w metodzie Main aplikacji:

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

Po drugie, po znalezieniu narzędzia EF Core DiagnosticListener zostanie utworzony nowy obserwator klucz-wartość w celu zasubskrybowania rzeczywistych zdarzeń platformy EF Core. Przykład:

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

Metoda OnNext jest tym razem wywoływana z parą klucz/wartość dla każdego zdarzenia platformy EF Core. Klucz jest nazwą zdarzenia, które można uzyskać z jednego z następujących elementów:

  • CoreEventId dla zdarzeń typowych dla wszystkich dostawców baz danych EF Core
  • RelationalEventId dla zdarzeń wspólnych dla wszystkich dostawców relacyjnej bazy danych
  • Podobna klasa dla zdarzeń specyficznych dla bieżącego dostawcy bazy danych. Na przykład SqlServerEventId dla dostawcy programu SQL Server.

Wartość pary klucz/wartość jest typem ładunku specyficznym dla zdarzenia. Typ ładunku, który ma być oczekiwany, jest udokumentowany dla każdego zdarzenia zdefiniowanego w tych klasach zdarzeń.

Na przykład powyższy kod obsługuje ContextInitialized zdarzenia i ConnectionOpening . W przypadku pierwszego z tych elementów ładunek to ContextInitializedEventData. Dla drugiego parametru jest to ConnectionEventData.

Napiwek

Funkcja ToString jest zastępowana w każdej klasie danych zdarzeń programu EF Core w celu wygenerowania równoważnego komunikatu dziennika dla zdarzenia. Na przykład wywołanie ContextInitializedEventData.ToString generuje element "Entity Framework Core 5.0.0 zainicjowane "BlogsContext" przy użyciu dostawcy "Microsoft.EntityFrameworkCore.Sqlite" z opcjami: Brak.

Przykład zawiera prostą aplikację konsolową, która wprowadza zmiany w bazie danych rejestrowania i wyświetla napotkane zdarzenia diagnostyczne.

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

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

        context.Add(
            new Blog { Name = "EF Blog", Posts = { new Post { Title = "EF Core 3.1!" }, new Post { 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 { Title = "EF Core 6.0!" });

        context.SaveChanges();
    }

Dane wyjściowe z tego kodu pokazują wykryte zdarzenia:

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