SQL-Abfragen vom Entity Framework loggen

SQL-Abfragen vom Entity Framework loggen
Page content

In diesem Artikel sehen wir uns an, wie wir die SQL-Abfragen und Befehle vom Entity Framework loggen. Das ist manchmal notwendig, um Fehler bei der Erstellung oder Abfrage von Daten zu identifizieren.

Schnelleinstieg/Grundlagen

Das Entity Framework ist ein Objekt-Relationaler Mapper (ORM). Solche ORMs wandeln sozusagen eine Datenbank und SQL-Abfragen in Objekte und Methoden um.

Im einfachsten Falle gibt es eine Klasse Person mit den Feldern Vorname, Nachname und ID. Ein ORM wandelt diese Klasse in eine Tabelle mit 3 Spalten um. ORM-Frameworks stellen Möglichkeiten bereit, sodass diese Daten beispielsweise durch MyDatabase.Person.SelectAll() abgerufen werden können und im Code eine Liste zurückgegeben wird.

Manchmal kommt es bei einer Abfrage zu Problemen. Hier bietet es sich manchmal an, den erstellten SQL-Code anzusehen und ggfs. selbst in einem Programm zur Datenbankverwaltung auszuführen.

Logging aktivieren

Entity Framework

Hier ist das Logging recht schnell implementiert. Dazu verwenden wir die Log-Methode im DbContext.

public class MyDbContext : DbContext {

  public MyDbContext() {
    this.Database.Log = (x) => Console.WriteLine(x);
  }

  public DbSet People { get; set; }

  }

Hinweis: Das Entity Framework steht nur in .NET Framework Projekten zur Verfügung. NET Core oder Standard können nur Entity Framework Core verwenden.

Entity Framework Core

Das Logging wurde für .NET Core umgestellt. Sehr viele Klassen nutzen nun die Standardinterfaces von Microsoft. In diesem Falle ist es ILogger . Wie im oberen Beispiel erstellen wir einen DbContext. Allerdings konfigurieren wir das Logging in der OnConfiguring-Methode.

public class MyDbContext : DbContext {

  public MyDbContext(){
  }

  public DbSet People { get; set; }

  protected override void OnConfiguring(DbContextOptionsBuilder builder) {
    builder.UseLoggerFactory(new LoggerFactory(new[] { new MyLoggerProvider() }));
    // Weitere Konfigurationen..
  }
}

Die Klasse LoggerFactory wird durch das Entity Framework bereitgestellt. MyLoggerProvider ist unser Provider, welcher unsere Log-Klasse erstellt. Die beiden Klassen sehen folgendermaßen aus:

public class MyLoggerProvider : ILoggerProvider {

  public void Dispose() {
  }

  public ILogger CreateLogger(string categoryName) {
    return new ConsoleLogger();
  }

}

public class ConsoleLogger : ILogger {

  private LoggerExternalScopeProvider _scope = new LoggerExternalScopeProvider();

  public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) {
    formatter = formatter ?? ((o, ex) => $"State: {o} - Exception: {ex}");
    Trace.WriteLine($" {logLevel}: {eventId} - {formatter(state, exception)}");
  }

  public bool IsEnabled(LogLevel logLevel) {
    return true;
  }

  public IDisposable BeginScope(TState state) {
    return \_scope?.Push(state) ?? NullScope.Instance;
  }

}

Beim ConsoleLogger habe ich mich an der Implementierung vom Microsoft-ConsoleLogger orientiert.

Die Methode IsEnabled kann für jeden Typen (Debug, Info, Warnung) zurückgeben, ob geloggt werden soll oder nicht. Die Scopes wurden mit .NET Core eingeführt. Es gibt eine Hierarchie, welche eine dynamische Konfiguration ermöglicht. Allerdings bin ich nicht in der Tiefe mit den Scopes vertraut und habe bisher „fertige“ Frameworks wie Log4Net oder NLog verwendet.

Die Ausgabe

Die Ausgabe meines .NET Core Konsolenprojektes sieht so aus (Auszug):

Debug: Microsoft.EntityFrameworkCore.Database.Command.CommandExecuting - Executing DbCommand [Parameters=[], CommandType='Text', CommandTimeout='30']
                    SELECT CASE WHEN COUNT(*) = 0 THEN FALSE ELSE TRUE END
                    FROM information_schema.tables
                    WHERE table_type = 'BASE TABLE' AND table_schema = 'MyDatabase'

Debug: Microsoft.EntityFrameworkCore.Database.Command.CommandExecuting - Executing DbCommand [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT `p`.`ID`, `p`.`FirstName`, `p`.`LastName`
FROM `People` AS `p`

Wie man sehen kann werden beispielsweise alle Tabellen der gewählten Datenbank ermittelt. Für das Beispiel habe ich ein Code-First Projekt erstellt. Dabei prüft das Entity Framework, ob alle notwendigen Tabellen vorhanden sind.

Später frage ich im Beispielcode alle Personen ab. Diese SQL-Abfrage ist schön im Log zu sehen.

Fazit

Wir haben uns angesehen, wie wir die Abfragen vom Entity Framework auf der Konsole ausgeben können. Dieses Ziel zu erreichen ist im „klassischen“ Entity Framework einfacher möglich als im Entity Framework Core.

Musstet ihr bereits Datenbankabfragen von ORMs nachschauen? Welche Werkzeuge benutzt ihr dafür?