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 DbContextOptionsDbContext 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.Commande 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));