Поделиться через


Использование диагностических слушателей в 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' с параметрами: Никакие".

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

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

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

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