Использование прослушивателей диагностики в EF Core

Совет

Пример этой статьи можно скачать на сайте GitHub.

Прослушиватели диагностики позволяют прослушивать любое событие EF Core, возникающее в текущем процессе .NET. Класс DiagnosticListener является частью общего механизма в .NET для получения диагностических сведений от запущенных приложений.

Прослушиватели диагностики не подходят для получения событий из одного экземпляра DbContext. Перехватчики EF Core предоставляют доступ к тем же событиям с регистрацией в контексте.

Прослушиватели диагностики не предназначены для ведения журнала. Рекомендуется использовать простое ведение журнала или Microsoft.Extensions.Logging для ведения журнала.

Пример. Наблюдение за событиями диагностики

Разрешение событий EF Core — это двухэтапный процесс. Во-первых, необходимо создать наблюдателя: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());
        }
    }
}

Метод ищет средство DiagnosticListener, полученное OnNext из EF Core. Этот прослушиватель имеет имя Microsoft.EntityFrameworkCore, которое можно получить из DbLoggerCategory класса, как показано ниже.

Затем этот наблюдатель должен быть зарегистрирован глобально, например в методе приложения Main :

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

Во-вторых, после обнаружения средства диагностики EF Core создается новый наблюдатель "ключ-значение", чтобы подписаться на фактические события EF Core. Например:

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

Этот OnNext метод вызывается с парой "ключ-значение" для каждого события EF Core. Ключ — это имя события, которое можно получить из одного из следующих элементов:

  • CoreEventId для событий, общих для всех поставщиков баз данных EF Core
  • RelationalEventId для событий, общих для всех поставщиков реляционных баз данных
  • Аналогичный класс для событий, относящихся к текущему поставщику базы данных. Например, SqlServerEventId для поставщика SQL Server.

Значение пары "ключ-значение" — это тип полезных данных, характерный для события. Тип ожидаемых полезных данных задокументирован для каждого события, определенного в этих классах событий.

Например, приведенный выше код обрабатывает ContextInitialized события и ConnectionOpening события. Для первого из них полезные данные — ContextInitializedEventDataэто полезные данные. Во-вторых, это ConnectionEventData.

Совет

ToString переопределяется в каждом классе данных событий EF Core, чтобы создать эквивалентное сообщение журнала для события. Например, при вызове ContextInitializedEventData.ToString возникает ошибка Entity Framework Core 5.0.0, инициализированная "BlogsContext" с помощью поставщика Microsoft.EntityFrameworkCore.Sqlite с параметрами: None.

В примере содержится простое консольное приложение, которое вносит изменения в базу данных блога и выводит возникшие диагностические события.

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

Выходные данные этого кода показывают обнаруженные события:

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