Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Sugerencia
Puede descargar el ejemplo de este artículo desde GitHub.
El registro sencillo de Entity Framework Core (EF Core) se puede usar para obtener fácilmente registros al desarrollar y depurar aplicaciones. Esta forma de registro requiere una configuración mínima y ningún paquete NuGet adicional.
Sugerencia
EF Core también se integra con Microsoft.Extensions.Logging, que requiere más configuración, pero a menudo es más adecuado para el registro en aplicaciones de producción.
Configuración
Se puede acceder a los registros de EF Core desde cualquier tipo de aplicación mediante el uso de LogTo al configurar una instancia de DbContext. Esta configuración se realiza normalmente en una sobreescritura de DbContext.OnConfiguring. Por ejemplo:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(Console.WriteLine);
Como alternativa, LogTo
se puede llamar como parte de AddDbContext o al crear una DbContextOptions instancia para pasar al DbContext
constructor.
Sugerencia
Se sigue llamando a OnConfiguring cuando se usa AddDbContext o se pasa una instancia dbContextOptions al constructor DbContext. Esto hace que sea el lugar ideal para aplicar la configuración de contexto independientemente de cómo se construye DbContext.
Dirigir los registros
Registro en la consola
LogTo
requiere un Action<T> delegado que acepte una cadena. EF Core llamará a este delegado con una cadena para cada mensaje de registro generado. A continuación, es necesario que el delegado haga algo con el mensaje especificado.
El Console.WriteLine método se usa a menudo para este delegado, como se muestra anteriormente. Esto da como resultado que cada mensaje de registro se escriba en la consola.
Registro en la ventana de depuración
Debug.WriteLine se puede usar para enviar la salida a la ventana Depurar en Visual Studio u otros IDE.
La sintaxis lambda debe usarse en este caso porque la Debug
clase se compila fuera de las compilaciones de versión. Por ejemplo:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(message => Debug.WriteLine(message));
Registro en un archivo
Escribir en un archivo requiere crear un StreamWriter o algo similar. A continuación, el WriteLine método se puede usar como en los otros ejemplos anteriores. Recuerde asegurarse de que el archivo se cierra limpiamente eliminando el escritor cuando se elimina el contexto. Por ejemplo:
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();
}
Sugerencia
Considere la posibilidad de usar Microsoft.Extensions.Logging para iniciar sesión en archivos en aplicaciones de producción.
Obtención de mensajes detallados
Datos confidenciales
De forma predeterminada, EF Core no incluirá los valores de ningún dato en los mensajes de excepción. Esto se debe a que estos datos pueden ser confidenciales y podrían revelarse en el uso de producción si no se controla una excepción.
Sin embargo, conocer los valores de los datos, especialmente de las claves, puede ser muy útil al depurar. Esto se puede habilitar en EF Core llamando a EnableSensitiveDataLogging(). Por ejemplo:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine)
.EnableSensitiveDataLogging();
Excepciones de consulta detalladas
Por motivos de rendimiento, EF Core no envuelve cada llamada para leer un valor del proveedor de base de datos en un bloque try-catch. Sin embargo, esto a veces da como resultado excepciones difíciles de diagnosticar, especialmente cuando la base de datos devuelve un valor NULL cuando el modelo no lo permite.
La activación de EnableDetailedErrors hará que EF introduzca los bloques de try-catch y, por tanto, proporcione errores más detallados. Por ejemplo:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine)
.EnableDetailedErrors();
Filtros
Niveles de registro
Cada mensaje de registro de EF Core se asigna a un nivel definido por la LogLevel enumeración. De forma predeterminada, el registro simple de EF Core incluye todos los mensajes en el nivel Debug
o superiores.
LogTo
se puede pasar un nivel mínimo superior para filtrar algunos mensajes. Por ejemplo, al pasar Information
, se obtienen un conjunto mínimo de registros limitados al acceso a la base de datos y algunos mensajes administrativos.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
Mensajes específicos
A cada mensaje de registro se le asigna un EventId. Se puede acceder a estos identificadores desde la clase CoreEventId o la clase RelationalEventId para mensajes específicos de relación. Un proveedor de base de datos también puede tener identificadores específicos del proveedor en una clase similar. Por ejemplo, SqlServerEventId para el proveedor de SQL Server.
LogTo
se puede configurar para registrar solo los mensajes asociados a uno o varios identificadores de evento. Por ejemplo, para registrar solo los mensajes del contexto que se inicializa o se elimina:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine, new[] { CoreEventId.ContextDisposed, CoreEventId.ContextInitialized });
Categorías de mensajes
Cada mensaje de registro se asigna a una categoría de registrador jerárquico con nombre. Las categorías son:
Categoría | Mensajes |
---|---|
Microsoft.EntityFrameworkCore | Todos los mensajes de EF Core |
Microsoft.EntityFrameworkCore.Database | Todas las interacciones de la base de datos |
Microsoft.EntityFrameworkCore.Database.Connection | Usos de una conexión de base de datos |
Microsoft.EntityFrameworkCore.Database.Command | Usos de un comando de base de datos |
Microsoft.EntityFrameworkCore.Database.Transaction | Usos de una transacción de base de datos |
Microsoft.EntityFrameworkCore.Update | Guardar entidades, excluyendo interacciones con la base de datos |
Microsoft.EntityFrameworkCore.Model | Todas las interacciones de modelo y metadatos |
Microsoft.EntityFrameworkCore.Model.Validation | Validación de modelos |
Microsoft.EntityFrameworkCore.Query | Consultas, excepto las interacciones de la base de datos |
Microsoft.EntityFrameworkCore.Infrastructure | Eventos generales, como la creación de contextos |
Microsoft.EntityFrameworkCore.Scaffolding | Ingeniería inversa de base de datos |
Microsoft.EntityFrameworkCore.Migrations | Migraciones |
Microsoft.EntityFrameworkCore.ChangeTracking | Interacciones de seguimiento de cambios |
LogTo
se puede configurar para registrar solo los mensajes de una o varias categorías. Por ejemplo, para registrar solo las interacciones de la base de datos:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine, new[] { DbLoggerCategory.Database.Name });
Tenga en cuenta que la DbLoggerCategory clase proporciona una API jerárquica para buscar una categoría y evita la necesidad de codificar cadenas de forma rígida.
Dado que las categorías son jerárquicas, en este ejemplo, la categoría Database
incluirá todos los mensajes de las subcategorías Database.Connection
, Database.Command
y Database.Transaction
.
Filtros personalizados
LogTo
permite usar un filtro personalizado para los casos en los que ninguna de las opciones de filtrado anteriores sea suficiente. Por ejemplo, para registrar cualquier mensaje en el nivel Information
o superior, así como mensajes para abrir y cerrar una conexión:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(
Console.WriteLine,
(eventId, logLevel) => logLevel >= LogLevel.Information
|| eventId == RelationalEventId.ConnectionOpened
|| eventId == RelationalEventId.ConnectionClosed);
Sugerencia
El filtrado mediante filtros personalizados o el uso de cualquiera de las otras opciones que se muestran aquí es más eficaz que el filtrado en el LogTo
delegado. Esto se debe a que si el filtro determina que el mensaje no se debe registrar, ni siquiera se crea el mensaje de registro.
Configuración de mensajes específicos
La API de EF Core ConfigureWarnings permite a las aplicaciones cambiar lo que sucede cuando se encuentra un evento específico. Esto se puede usar para:
- Cambio del nivel de registro en el que se registra el evento
- Omitir el registro del evento por completo
- Lanzar una excepción cuando se produce el evento
Cambio del nivel de registro de un evento
En el ejemplo anterior se usó un filtro personalizado para registrar todos los mensajes, LogLevel.Information
así como dos eventos definidos para LogLevel.Debug
. Se puede lograr lo mismo cambiando el nivel de registro de los dos Debug
eventos a 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);
Suprimir el registro de un evento
De forma similar, se puede suprimir un evento individual del registro. Esto es especialmente útil para ignorar una advertencia que se ha revisado y comprendido. Por ejemplo:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.ConfigureWarnings(b => b.Ignore(CoreEventId.DetachedLazyLoadingWarning))
.LogTo(Console.WriteLine);
Organizar un evento
Por último, EF Core se puede configurar para iniciar un evento determinado. Esto resulta especialmente útil para cambiar una advertencia a un error. (De hecho, este era el propósito original del ConfigureWarnings
método, por lo tanto el nombre). Por ejemplo:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.ConfigureWarnings(b => b.Throw(RelationalEventId.MultipleCollectionIncludeWarning))
.LogTo(Console.WriteLine);
Contenido y formato de mensajes
El contenido predeterminado de LogTo
está formateado a través de varias líneas. La primera línea contiene metadatos de mensaje:
- LogLevel como prefijo de cuatro caracteres
- Marca de tiempo local, en un formato adecuado para la cultura actual
- El EventId en el formulario que se puede copiar/pegar para obtener el miembro de CoreEventId o de las otras clases
EventId
, además del valor ID sin formato - Categoría de eventos, como se ha descrito anteriormente.
Por ejemplo:
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.
Este contenido se puede personalizar pasando valores de DbContextLoggerOptions, como se muestra en las secciones siguientes.
Sugerencia
Considere la posibilidad de usar Microsoft.Extensions.Logging para obtener más control sobre el formato de registro.
Uso de la hora UTC
De forma predeterminada, las marcas de tiempo están diseñadas para el uso local durante la depuración. Usa DbContextLoggerOptions.DefaultWithUtcTime para usar marcas de tiempo UTC independientes de la cultura en su lugar, pero mantén todo lo demás igual. Por ejemplo:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(
Console.WriteLine,
LogLevel.Debug,
DbContextLoggerOptions.DefaultWithUtcTime);
Este ejemplo da como resultado el siguiente formato de registro:
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.
Registro de una sola línea
A veces resulta útil obtener exactamente una línea por mensaje de registro. Esto se puede habilitar mediante DbContextLoggerOptions.SingleLine. Por ejemplo:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(
Console.WriteLine,
LogLevel.Debug,
DbContextLoggerOptions.DefaultWithLocalTime | DbContextLoggerOptions.SingleLine);
Este ejemplo da como resultado el siguiente formato de registro:
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.
Otras opciones de contenido
Otras marcas de DbContextLoggerOptions se pueden usar para reducir la cantidad de metadatos incluidos en el registro. Esto puede ser útil junto con el registro de una sola línea. Por ejemplo:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(
Console.WriteLine,
LogLevel.Debug,
DbContextLoggerOptions.UtcTime | DbContextLoggerOptions.SingleLine);
Este ejemplo da como resultado el siguiente formato de registro:
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.
Migración desde EF6
El registro simple de EF Core difiere de Database.Log en EF6 de dos maneras importantes:
- Los mensajes de registro no se limitan solo a las interacciones de la base de datos
- El registro debe configurarse en el momento de inicialización del contexto.
Para la primera diferencia, el filtrado descrito anteriormente se puede usar para limitar qué mensajes se registran.
La segunda diferencia es un cambio intencionado para mejorar el rendimiento al no generar mensajes de registro cuando no son necesarios. Sin embargo, todavía es posible obtener un comportamiento similar a EF6 mediante la creación de una Log
propiedad en DbContext
y, a continuación, usarla solo cuando se ha establecido. Por ejemplo:
public Action<string> Log { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(s => Log?.Invoke(s));