Registrazione semplice
Suggerimento
È possibile scaricare l'esempio di questo articolo da GitHub.
La registrazione semplice di Entity Framework Core (EF Core) può essere usata per ottenere facilmente i log durante lo sviluppo e il debug di applicazioni. Questa forma di registrazione richiede una configurazione minima e nessun pacchetto NuGet aggiuntivo.
Suggerimento
EF Core si integra anche con Microsoft.Extensions.Logging, che richiede più configurazione, ma è spesso più adatto per la registrazione nelle applicazioni di produzione.
Configurazione
È possibile accedere ai log di EF Core da qualsiasi tipo di applicazione tramite l'uso di LogTo quando si configura un'istanza di DbContext. Questa configurazione viene eseguita in genere in un override di DbContext.OnConfiguring. Ad esempio:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(Console.WriteLine);
In alternativa, LogTo
può essere chiamato come parte di AddDbContext o quando si crea un'istanza DbContextOptions DbContext
da passare al costruttore.
Suggerimento
OnConfiguring viene comunque chiamato quando si usa AddDbContext o un'istanza DbContextOptions viene passata al costruttore DbContext. Ciò rende la posizione ideale per applicare la configurazione del contesto indipendentemente dalla modalità di costruzione di DbContext.
Indirizzamento dei log
Registrazione nella console
LogTo
richiede un Action<T> delegato che accetta una stringa. EF Core chiamerà questo delegato con una stringa per ogni messaggio di log generato. Spetta quindi al delegato eseguire un'operazione con il messaggio specificato.
Il Console.WriteLine metodo viene spesso usato per questo delegato, come illustrato in precedenza. Ciò comporta la scrittura di ogni messaggio di log nella console.
Registrazione nella finestra di debug
Debug.WriteLine può essere usato per inviare l'output alla finestra Debug in Visual Studio o in altri IDE. In questo caso è necessario usare la sintassi lambda perché la Debug
classe viene compilata fuori dalle build di versione. Ad esempio:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(message => Debug.WriteLine(message));
Registrazione in un file
La scrittura in un file richiede la creazione di un oggetto o un StreamWriter file simile per il file. Il WriteLine metodo può quindi essere usato come negli altri esempi precedenti. Ricordarsi di assicurarsi che il file venga chiuso in modo pulito eliminando il writer quando il contesto viene eliminato. Ad esempio:
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();
}
Suggerimento
Prendere in considerazione l'uso di Microsoft.Extensions.Logging per la registrazione ai file nelle applicazioni di produzione.
Recupero di messaggi dettagliati
Dati sensibili
Per impostazione predefinita, EF Core non includerà i valori dei dati nei messaggi di eccezione. Ciò è dovuto al fatto che tali dati possono essere riservati e potrebbero essere rivelati nell'uso in produzione se non viene gestita un'eccezione.
Tuttavia, conoscere i valori dei dati, soprattutto per le chiavi, può essere molto utile durante il debug. Questa opzione può essere abilitata in EF Core chiamando EnableSensitiveDataLogging(). Ad esempio:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine)
.EnableSensitiveDataLogging();
Eccezioni di query dettagliate
Per motivi di prestazioni, EF Core non esegue il wrapping di ogni chiamata per leggere un valore dal provider di database in un blocco try-catch. Tuttavia, ciò comporta talvolta eccezioni difficili da diagnosticare, soprattutto quando il database restituisce un valore NULL quando non è consentito dal modello.
L'attivazione EnableDetailedErrors causerà l'introduzione di questi blocchi try-catch da parte di Entity Framework e in tal modo fornirà errori più dettagliati. Ad esempio:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine)
.EnableDetailedErrors();
Filtri
Livelli di registrazione
Ogni messaggio di log di EF Core viene assegnato a un livello definito dall'enumerazione LogLevel . Per impostazione predefinita, la registrazione semplice di EF Core include ogni messaggio a Debug
livello o superiore. LogTo
può essere passato un livello minimo superiore per filtrare alcuni messaggi. Ad esempio, il Information
passaggio di risultati in un set minimo di log limitato all'accesso al database e ad alcuni messaggi di manutenzione.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
Messaggi specifici
A ogni messaggio di log viene assegnato un oggetto EventId. È possibile accedere a questi ID dalla CoreEventId classe o dalla RelationalEventId classe per i messaggi specifici del relazionale. Un provider di database può anche avere ID specifici del provider in una classe simile. Ad esempio, SqlServerEventId per il provider SQL Server.
LogTo
può essere configurato per registrare solo i messaggi associati a uno o più ID evento. Ad esempio, per registrare solo i messaggi per il contesto da inizializzare o eliminare:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine, new[] { CoreEventId.ContextDisposed, CoreEventId.ContextInitialized });
Categorie di messaggi
Ogni messaggio di log viene assegnato a una categoria di logger gerarchica denominata. Le categorie sono le seguenti:
Categoria | Messaggi |
---|---|
Microsoft.EntityFrameworkCore | Tutti i messaggi di EF Core |
Microsoft.EntityFrameworkCore.Database | Tutte le interazioni del database |
Microsoft.EntityFrameworkCore.Database. Connessione | Uso di una connessione al database |
Microsoft.EntityFrameworkCore.Database.Command | Usi di un comando di database |
Microsoft.EntityFrameworkCore.Database.Transaction | Usi di una transazione di database |
Microsoft.EntityFrameworkCore.Update | Salvataggio di entità, esclusione delle interazioni del database |
Microsoft.EntityFrameworkCore.Model | Tutte le interazioni tra modelli e metadati |
Microsoft.EntityFrameworkCore.Model.Validation | Convalida modello |
Microsoft.EntityFrameworkCore.Query | Query, escluse le interazioni con il database |
Microsoft.EntityFrameworkCore.Infrastructure | Eventi generali, ad esempio la creazione del contesto |
Microsoft.EntityFrameworkCore.Scaffolding | Reverse engineering del database |
Microsoft.EntityFrameworkCore.Migrations | Migrazioni |
Microsoft.EntityFrameworkCore.ChangeTracking | Interazioni di rilevamento delle modifiche |
LogTo
può essere configurato in modo da registrare solo i messaggi da una o più categorie. Ad esempio, per registrare solo le interazioni del database:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine, new[] { DbLoggerCategory.Database.Name });
Si noti che la DbLoggerCategory classe fornisce un'API gerarchica per trovare una categoria ed evita la necessità di impostare stringhe hardcoded.
Poiché le categorie sono gerarchica, questo esempio che usa la Database
categoria includerà tutti i messaggi per le sottocategorie Database.Connection
, Database.Command
e Database.Transaction
.
Filtri personalizzati
LogTo
consente l'uso di un filtro personalizzato per i casi in cui nessuna delle opzioni di filtro precedenti è sufficiente. Ad esempio, per registrare qualsiasi messaggio a livello Information
o superiore, nonché messaggi per l'apertura e la chiusura di una connessione:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(
Console.WriteLine,
(eventId, logLevel) => logLevel >= LogLevel.Information
|| eventId == RelationalEventId.ConnectionOpened
|| eventId == RelationalEventId.ConnectionClosed);
Suggerimento
Il filtro tramite filtri personalizzati o l'uso di una qualsiasi delle altre opzioni illustrate di seguito è più efficiente rispetto al filtro nel LogTo
delegato. Ciò è dovuto al fatto che se il filtro determina che il messaggio non deve essere registrato, il messaggio di log non viene nemmeno creato.
Configurazione per messaggi specifici
L'API EF Core ConfigureWarnings consente alle applicazioni di modificare ciò che accade quando viene rilevato un evento specifico. Questo può essere usato per:
- Modificare il livello di log a cui viene registrato l'evento
- Ignorare completamente la registrazione dell'evento
- Generare un'eccezione quando si verifica l'evento
Modifica del livello di log per un evento
Nell'esempio precedente è stato usato un filtro personalizzato per registrare ogni messaggio in LogLevel.Information
e due eventi definiti per LogLevel.Debug
. Lo stesso risultato può essere ottenuto modificando il livello di log dei due Debug
eventi in 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);
Eliminare la registrazione di un evento
In modo analogo, un singolo evento può essere eliminato dalla registrazione. Ciò è particolarmente utile per ignorare un avviso che è stato esaminato e compreso. Ad esempio:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.ConfigureWarnings(b => b.Ignore(CoreEventId.DetachedLazyLoadingWarning))
.LogTo(Console.WriteLine);
Generare un evento
Infine, EF Core può essere configurato per generare un determinato evento. Ciò è particolarmente utile per modificare un avviso in un errore. (Infatti, questo era lo scopo originale del ConfigureWarnings
metodo, quindi il nome. Per esempio:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.ConfigureWarnings(b => b.Throw(RelationalEventId.MultipleCollectionIncludeWarning))
.LogTo(Console.WriteLine);
Contenuto e formattazione dei messaggi
Il contenuto predefinito di LogTo
viene formattato tra più righe. La prima riga contiene i metadati del messaggio:
- Oggetto LogLevel come prefisso a quattro caratteri
- Timestamp locale, formattato per le impostazioni cultura correnti
- Oggetto EventId nel formato che può essere copiato/incollato per ottenere il membro da CoreEventId o una delle altre
EventId
classi, oltre al valore ID non elaborato - Categoria di eventi, come descritto in precedenza.
Ad esempio:
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.
Questo contenuto può essere personalizzato passando i valori da DbContextLoggerOptions, come illustrato nelle sezioni seguenti.
Suggerimento
Prendere in considerazione l'uso di Microsoft.Extensions.Logging per un maggiore controllo sulla formattazione dei log.
Uso dell'ora UTC
Per impostazione predefinita, i timestamp sono progettati per l'utilizzo locale durante il debug. Usare DbContextLoggerOptions.DefaultWithUtcTime invece per usare i timestamp UTC indipendenti dalle impostazioni cultura, ma mantenere tutto lo stesso. Ad esempio:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(
Console.WriteLine,
LogLevel.Debug,
DbContextLoggerOptions.DefaultWithUtcTime);
In questo esempio viene restituita la formattazione del log seguente:
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.
Registrazione a riga singola
A volte è utile ottenere esattamente una riga per ogni messaggio di log. Questa opzione può essere abilitata da DbContextLoggerOptions.SingleLine. Ad esempio:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(
Console.WriteLine,
LogLevel.Debug,
DbContextLoggerOptions.DefaultWithLocalTime | DbContextLoggerOptions.SingleLine);
In questo esempio viene restituita la formattazione del log seguente:
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.
Altre opzioni di contenuto
È possibile usare altri flag in DbContextLoggerOptions per ridurre la quantità di metadati inclusi nel log. Ciò può essere utile insieme alla registrazione a riga singola. Ad esempio:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(
Console.WriteLine,
LogLevel.Debug,
DbContextLoggerOptions.UtcTime | DbContextLoggerOptions.SingleLine);
In questo esempio viene restituita la formattazione del log seguente:
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.
Passaggio da EF6
La registrazione semplice di EF Core è diversa da Database.Log ef6 in due modi importanti:
- I messaggi di log non sono limitati solo alle interazioni del database
- La registrazione deve essere configurata in fase di inizializzazione del contesto
Per la prima differenza, è possibile usare il filtro descritto in precedenza per limitare i messaggi registrati.
La seconda differenza è una modifica intenzionale per migliorare le prestazioni non generando messaggi di log quando non sono necessari. Tuttavia, è comunque possibile ottenere un comportamento simile a EF6 creando una Log
proprietà nell'oggetto DbContext
e quindi usandola solo quando è stata impostata. Ad esempio:
public Action<string> Log { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(s => Log?.Invoke(s));