Utilisation des écouteurs de diagnostic dans EF Core
Conseil
Vous pouvez télécharger l’exemple de cet article à partir de GitHub.
Les écouteurs de diagnostic permettent d’écouter n’importe quel événement EF Core qui se produit dans le processus .NET actuel. La classe DiagnosticListener fait partie d’un mécanisme commun dans .NET pour obtenir des informations de diagnostic à partir d’applications en cours d’exécution.
Les écouteurs de diagnostic ne conviennent pas à l’obtention d’événements à partir d’une instance DbContext unique. Les intercepteurs EF Core fournissent l’accès aux mêmes événements avec l’inscription par contexte.
Les écouteurs de diagnostic ne sont pas conçus pour la journalisation. Envisagez d’utiliser journalisation simple ou Microsoft.Extensions.Logging pour la journalisation.
Exemple : Observation d’événements de diagnostic
La résolution des événements EF Core est un processus en deux étapes. Tout d’abord, un observateur pour DiagnosticListener
lui-même doit être créé :
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());
}
}
}
La méthode OnNext
recherche le DiagnosticListener qui provient d’EF Core. Cet écouteur porte le nom « Microsoft.EntityFrameworkCore », qui peut être obtenu à partir de la classe DbLoggerCategory, comme indiqué.
Cet observateur doit ensuite être inscrit globalement, par exemple dans la méthode Main
de l’application :
DiagnosticListener.AllListeners.Subscribe(new DiagnosticObserver());
Deuxièmement, une fois que EF Core DiagnosticListener est trouvé, un nouvel observateur clé-valeur est créé pour s’abonner aux événements EF Core réels. Par exemple :
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} ");
}
}
}
La méthode OnNext
est cette fois appelée avec une paire clé/valeur pour chaque événement EF Core. La clé est le nom de l’événement, qui peut être obtenu à partir de l’un des éléments suivants :
- CoreEventId pour les événements communs à tous les fournisseurs de base de données EF Core
- RelationalEventId pour les événements communs à tous les fournisseurs de base de données relationnelles
- Classe similaire pour les événements spécifiques au fournisseur de base de données actuel. Par exemple, SqlServerEventId pour le fournisseur SQL Server.
La valeur de la paire clé/valeur est un type de charge utile spécifique à l’événement. Le type de charge utile à attendre est documenté sur chaque événement défini dans ces classes d’événements.
Par exemple, le code ci-dessus gère les ContextInitialized et les événements ConnectionOpening. Pour la première de ces opérations, la charge utile est ContextInitializedEventData. Pour la seconde, c’est ConnectionEventData.
Conseil
ToString est substitué dans chaque classe de données d’événement EF Core pour générer le message de journal équivalent pour l’événement. Par exemple, l’appel de ContextInitializedEventData.ToString
génère « Entity Framework Core 5.0.0 initialisé 'BlogsContext' à l’aide du fournisseur 'Microsoft.EntityFrameworkCore.Sqlite' avec des options : None ».
L’exemple contient une application console simple qui apporte des modifications à la base de données de blogs et imprime les événements de diagnostic rencontrés.
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();
}
La sortie de ce code affiche les événements détectés :
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