Uso di listener di diagnostica in EF Core
Suggerimento
È possibile scaricare l'esempio di questo articolo da GitHub.
I listener di diagnostica consentono l'ascolto di qualsiasi evento di EF Core che si verifica nel processo .NET corrente. La DiagnosticListener classe fa parte di un meccanismo comune in .NET per ottenere informazioni di diagnostica dalle applicazioni in esecuzione.
I listener di diagnostica non sono adatti per ottenere eventi da una singola istanza di DbContext. Gli intercettori di EF Core forniscono l'accesso agli stessi eventi con registrazione per contesto.
I listener di diagnostica non sono progettati per la registrazione. È consigliabile usare la registrazione semplice o Microsoft.Extensions.Logging per la registrazione.
Esempio: Osservazione degli eventi di diagnostica
La risoluzione degli eventi di EF Core è un processo in due passaggi. Innanzitutto, è necessario creare un osservatore per DiagnosticListener
se stesso:
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());
}
}
}
Il OnNext
metodo cerca l'oggetto DiagnosticListener proveniente da EF Core. Questo listener ha il nome "Microsoft.EntityFrameworkCore", che può essere ottenuto dalla DbLoggerCategory classe come illustrato.
Questo osservatore deve quindi essere registrato a livello globale, ad esempio nel metodo dell'applicazione Main
:
DiagnosticListener.AllListeners.Subscribe(new DiagnosticObserver());
In secondo luogo, dopo aver trovato EF Core DiagnosticListener, viene creato un nuovo osservatore chiave-valore per sottoscrivere gli eventi effettivi di EF Core. Ad esempio:
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} ");
}
}
}
Il OnNext
metodo è questa volta chiamato con una coppia chiave/valore per ogni evento EF Core. La chiave è il nome dell'evento, che può essere ottenuto da uno di:
- CoreEventId per gli eventi comuni a tutti i provider di database EF Core
- RelationalEventId per gli eventi comuni a tutti i provider di database relazionali
- Classe simile per gli eventi specifici del provider di database corrente. Ad esempio, SqlServerEventId per il provider SQL Server.
Il valore della coppia chiave/valore è un tipo di payload specifico dell'evento. Il tipo di payload previsto è documentato in ogni evento definito in queste classi di evento.
Ad esempio, il codice precedente gestisce gli ContextInitialized eventi e ConnectionOpening . Per il primo di questi, il payload è ContextInitializedEventData. Per il secondo, è ConnectionEventData.
Suggerimento
ToString viene sottoposto a override in ogni classe di dati di evento di EF Core per generare il messaggio di log equivalente per l'evento. Ad esempio, la chiamata ContextInitializedEventData.ToString
genera "Entity Framework Core 5.0.0 inizializzato "BlogsContext" usando il provider 'Microsoft.EntityFrameworkCore.Sqlite' con opzioni: Nessuno".
L'esempio contiene una semplice applicazione console che apporta modifiche al database di blogging e stampa gli eventi di diagnostica rilevati.
public static void Main()
{
DiagnosticListener.AllListeners.Subscribe(new DiagnosticObserver());
using (var context = new BlogsContext())
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
context.Add(
new Blog { Name = "EF Blog", Posts = { new Post { Title = "EF Core 3.1!" }, new Post { Title = "EF Core 5.0!" } } });
context.SaveChanges();
}
using (var context = new BlogsContext())
{
var blog = context.Blogs.Include(e => e.Posts).Single();
blog.Name = "EF Core Blog";
context.Remove(blog.Posts.First());
blog.Posts.Add(new Post { Title = "EF Core 6.0!" });
context.SaveChanges();
}
L'output di questo codice mostra gli eventi rilevati:
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