Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Подсказка
Пример этой статьи можно скачать на сайте GitHub.
Простое ведение журнала Entity Framework Core (EF Core) можно использовать для легкого получения журналов при разработке и отладке приложений. Эта форма ведения журнала требует минимальной конфигурации и не требует дополнительных пакетов NuGet.
Подсказка
EF Core также интегрируется с Microsoft.Extensions.Logging, которая требует больше конфигурации, но часто подходит для ведения журнала в рабочих приложениях.
Конфигурация
Журналы EF Core можно получить из любого типа приложения с помощью использования LogTo при настройке экземпляра DbContext. Конфигурация обычно осуществляется через переопределение DbContext.OnConfiguring. Рассмотрим пример.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(Console.WriteLine);
Кроме того, LogTo
можно вызывать как часть AddDbContext или при создании экземпляра DbContextOptions для передачи конструктору DbContext
.
Подсказка
Метод OnConfiguring по-прежнему вызывается, когда используется AddDbContext или экземпляр DbContextOptions передается в конструктор DbContext. Это делает его идеальным местом для применения конфигурации контекста независимо от того, как создается DbContext.
Перенаправление журналов
Логирование в консоль
LogTo
требует делегата Action<T> , который принимает строку. EF Core вызовет этот делегат со строкой для каждого созданного лог-сообщения. Затем делегат должен сделать что-то с заданным сообщением.
Метод Console.WriteLine часто используется для этого делегата, как показано выше. Это приводит к записи каждого сообщения журнала в консоль.
Логирование в окне отладки
Debug.WriteLine можно использовать для отправки выходных данных в окно отладки в Visual Studio или других средах разработки.
Лямбда-синтаксис должен использоваться в этом случае, так как Debug
класс компилируется из сборок выпуска. Рассмотрим пример.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(message => Debug.WriteLine(message));
Ведение журнала в файл
Запись в файл требует создания StreamWriter или подобного объекта для файла. Затем можно использовать этот метод WriteLine, как в других примерах выше. Не забудьте убедиться, что файл закрыт чисто путем удаления модуля записи при удалении контекста. Рассмотрим пример.
private readonly StreamWriter _logStream = new StreamWriter("mylog.txt", append: true);
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(_logStream.WriteLine);
public override void Dispose()
{
base.Dispose();
_logStream.Dispose();
}
public override async ValueTask DisposeAsync()
{
await base.DisposeAsync();
await _logStream.DisposeAsync();
}
Подсказка
Рекомендуется использовать Microsoft.Extensions.Logging для логирования в файлы в промышленных приложениях.
Получение подробных сообщений
Конфиденциальные данные
По умолчанию EF Core не будет включать значения любых данных в сообщениях об исключениях. Это связано с тем, что такие данные могут быть конфиденциальными и могут быть выявлены в рабочей среде, если исключение не обрабатывается.
Однако знание значений данных, особенно для ключей, может оказаться очень полезным при отладке. Это можно включить в EF Core путем вызова EnableSensitiveDataLogging(). Рассмотрим пример.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine)
.EnableSensitiveDataLogging();
Подробные исключения запросов
По соображениям производительности EF Core не упаковывает каждый вызов для чтения значения от поставщика базы данных в блоке try-catch. Однако иногда это приводит к возникновению исключений, которые трудно диагностировать, особенно если база данных возвращает значение NULL, если это не разрешено моделью.
Включение EnableDetailedErrors заставит EF вводить блоки try-catch, что приведет к более подробным сообщениям об ошибках. Рассмотрим пример.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine)
.EnableDetailedErrors();
Фильтрация
Уровни журнала
Каждое сообщение журнала EF Core имеет уровень, определенный перечислением LogLevel. По умолчанию простое ведение журнала EF Core включает каждое сообщение на Debug
уровне или выше.
LogTo
можно передать более высокий минимальный уровень, чтобы отфильтровать некоторые сообщения. Например, передача Information
приводит к минимальному набору журналов, ограниченному доступом к базе данных и некоторыми служебными сообщениями.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
Определенные сообщения
Каждому сообщению журнала назначается EventId. Эти идентификаторы можно получить из CoreEventId класса или RelationalEventId класса для реляционных сообщений. У поставщика базы данных могут быть идентификаторы, специфичные для поставщика, в аналогичном классе. Например, SqlServerEventId для поставщика SQL Server.
LogTo
можно настроить только журнал сообщений, связанных с одним или несколькими идентификаторами событий. Например, чтобы регистрировать только сообщения для контекста, который инициализируется или удаляется:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine, new[] { CoreEventId.ContextDisposed, CoreEventId.ContextInitialized });
Категории сообщений
Каждое сообщение журнала назначается именованной категории иерархического средства ведения журнала. Категории:
Категория | Сообщения |
---|---|
Microsoft.EntityFrameworkCore | Все сообщения EF Core |
Microsoft.EntityFrameworkCore.Database | Все взаимодействия с базой данных |
Microsoft.EntityFrameworkCore.Database.Connection | Использование подключения к базе данных |
Microsoft.EntityFrameworkCore.Database.Command | Использование команды базы данных |
Microsoft.EntityFrameworkCore.БазаДанных.Транзакция | Использование транзакции базы данных |
Microsoft.EntityFrameworkCore.Update | Сохранение объектов, за исключением взаимодействия с базой данных |
Microsoft.EntityFrameworkCore.Model | Все взаимодействия модели и метаданных |
Microsoft.EntityFrameworkCore.Model.Validation | Проверка модели |
Microsoft.EntityFrameworkCore.Query | Запросы, за исключением взаимодействия с базой данных |
Microsoft.EntityFrameworkCore.Infrastructure | Общие события, такие как создание контекста |
Microsoft.EntityFrameworkCore.Scaffolding | Реверсивная инженерия базы данных |
Microsoft.EntityFrameworkCore.Migrations | Миграции |
Microsoft.EntityFrameworkCore.ChangeTracking | Взаимодействие по отслеживанию изменений |
LogTo
можно настроить только для записи сообщений из одной или нескольких категорий. Например, чтобы регистрировать только взаимодействие с базой данных:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine, new[] { DbLoggerCategory.Database.Name });
Обратите внимание, что DbLoggerCategory класс предоставляет иерархический API для поиска категории и избегает необходимости жесткого кода строк.
Так как категории являются иерархическими, в этом примере с помощью Database
категории будут включены все сообщения для подкатегорий Database.Connection
, Database.Command
и Database.Transaction
.
Пользовательские фильтры
LogTo
позволяет использовать пользовательский фильтр для случаев, когда ни один из описанных выше параметров фильтрации не является достаточным. Например, чтобы записать любое сообщение на уровне Information
или выше, а также сообщения для открытия и закрытия подключения:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(
Console.WriteLine,
(eventId, logLevel) => logLevel >= LogLevel.Information
|| eventId == RelationalEventId.ConnectionOpened
|| eventId == RelationalEventId.ConnectionClosed);
Подсказка
Фильтрация с помощью пользовательских фильтров или использование других параметров, показанных здесь, более эффективна, чем фильтрация в делегате LogTo
. Это связано с тем, что если фильтр определяет, что сообщение не должно быть зарегистрировано, то сообщение журнала даже не создается.
Конфигурация для определенных сообщений
API EF Core ConfigureWarnings позволяет приложениям изменять то, что происходит при обнаружении определенного события. Это можно использовать для следующих способов:
- Изменение уровня журнала, на котором регистрируется событие
- Пропустить журналирование события совсем
- Создание исключения при возникновении события
Изменение уровня журнала для события
В предыдущем примере используется настраиваемый фильтр для регистрации каждого сообщения LogLevel.Information
, а также для двух событий, определенных для LogLevel.Debug
. Того же результата можно достигнуть, изменив уровень журнала двух Debug
событий на Information
:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.ConfigureWarnings(
b => b.Log(
(RelationalEventId.ConnectionOpened, LogLevel.Information),
(RelationalEventId.ConnectionClosed, LogLevel.Information)))
.LogTo(Console.WriteLine, LogLevel.Information);
Подавление ведения журнала события
Таким же образом можно отключить отдельное событие из ведения журнала. Это особенно полезно для игнорировать предупреждение, которое было проверено и понято. Рассмотрим пример.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.ConfigureWarnings(b => b.Ignore(CoreEventId.DetachedLazyLoadingWarning))
.LogTo(Console.WriteLine);
Организация мероприятия
Наконец, EF Core можно настроить для создания данного события. Это особенно полезно для изменения предупреждения в ошибку. (Действительно, это была исходная цель ConfigureWarnings
метода, следовательно, имя.) Например:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.ConfigureWarnings(b => b.Throw(RelationalEventId.MultipleCollectionIncludeWarning))
.LogTo(Console.WriteLine);
Содержимое сообщения и форматирование
Содержимое LogTo
по умолчанию отформатировано по нескольким строкам. Первая строка содержит метаданные сообщения:
- Префикс LogLevel с четырьмя символами
- Местное время, отформатированное для текущей культуры
- Форма EventId, которая может быть скопирована и вставлена для получения участника из CoreEventId или одного из других классов
EventId
, а также значение исходного идентификатора. - Категория событий, как описано выше.
Рассмотрим пример.
info: 10/6/2020 10:52:45.581 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE "Blogs" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_Blogs" PRIMARY KEY AUTOINCREMENT,
"Name" INTEGER NOT NULL
);
dbug: 10/6/2020 10:52:45.582 RelationalEventId.TransactionCommitting[20210] (Microsoft.EntityFrameworkCore.Database.Transaction)
Committing transaction.
dbug: 10/6/2020 10:52:45.585 RelationalEventId.TransactionCommitted[20202] (Microsoft.EntityFrameworkCore.Database.Transaction)
Committed transaction.
Это содержимое можно настроить, передав значения из DbContextLoggerOptions, как показано в следующих разделах.
Подсказка
Рассмотрите возможность использования Microsoft.Extensions.Logging для получения большего контроля над форматированием журнала.
Использование времени UTC
По умолчанию метки времени предназначены для локального потребления при отладке. Используйте DbContextLoggerOptions.DefaultWithUtcTime вместо этого метки времени UTC, не зависящие от языка и региональных параметров, но сохраняйте все остальное. Рассмотрим пример.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(
Console.WriteLine,
LogLevel.Debug,
DbContextLoggerOptions.DefaultWithUtcTime);
В этом примере приводится следующее форматирование журнала:
info: 2020-10-06T17:55:39.0333701Z RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE "Blogs" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_Blogs" PRIMARY KEY AUTOINCREMENT,
"Name" INTEGER NOT NULL
);
dbug: 2020-10-06T17:55:39.0333892Z RelationalEventId.TransactionCommitting[20210] (Microsoft.EntityFrameworkCore.Database.Transaction)
Committing transaction.
dbug: 2020-10-06T17:55:39.0351684Z RelationalEventId.TransactionCommitted[20202] (Microsoft.EntityFrameworkCore.Database.Transaction)
Committed transaction.
Однострочное логирование
Иногда полезно получить ровно одну строку для каждого сообщения журнала. Это можно включить с помощью DbContextLoggerOptions.SingleLine. Рассмотрим пример.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(
Console.WriteLine,
LogLevel.Debug,
DbContextLoggerOptions.DefaultWithLocalTime | DbContextLoggerOptions.SingleLine);
В этом примере приводится следующее форматирование журнала:
info: 10/6/2020 10:52:45.723 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) -> Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']CREATE TABLE "Blogs" ( "Id" INTEGER NOT NULL CONSTRAINT "PK_Blogs" PRIMARY KEY AUTOINCREMENT, "Name" INTEGER NOT NULL);
dbug: 10/6/2020 10:52:45.723 RelationalEventId.TransactionCommitting[20210] (Microsoft.EntityFrameworkCore.Database.Transaction) -> Committing transaction.
dbug: 10/6/2020 10:52:45.725 RelationalEventId.TransactionCommitted[20202] (Microsoft.EntityFrameworkCore.Database.Transaction) -> Committed transaction.
Другие параметры содержимого
Другие флаги DbContextLoggerOptions можно использовать для уменьшения объема метаданных, включенных в журнал. Это может быть полезно в сочетании с однострочным ведением журнала. Рассмотрим пример.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(
Console.WriteLine,
LogLevel.Debug,
DbContextLoggerOptions.UtcTime | DbContextLoggerOptions.SingleLine);
В этом примере приводится следующее форматирование журнала:
2020-10-06T17:52:45.7320362Z -> Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']CREATE TABLE "Blogs" ( "Id" INTEGER NOT NULL CONSTRAINT "PK_Blogs" PRIMARY KEY AUTOINCREMENT, "Name" INTEGER NOT NULL);
2020-10-06T17:52:45.7320531Z -> Committing transaction.
2020-10-06T17:52:45.7339441Z -> Committed transaction.
Переход из EF6
Простое ведение журналов в EF Core отличается от Database.Log в EF6 двумя важными способами:
- Сообщения журнала не ограничиваются только взаимодействием с базой данных
- Ведение журнала должно быть настроено во время инициализации контекста
В первую очередь фильтрацию, описанную выше, можно использовать для ограничения того, какие сообщения регистрируются.
Второе отличие заключается в намеренном изменении, направленном на улучшение производительности, путем прекращения генерации сообщений журнала, когда они не нужны. Тем не менее, по-прежнему можно получить аналогичное поведение с EF6, создав Log
свойство на вашем DbContext
устройстве, а затем используя его только в том случае, если оно установлено. Рассмотрим пример.
public Action<string> Log { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(s => Log?.Invoke(s));