Tipps und Tricks rund um .NET und Visual Studio

Speicheroperationen in Entity Framework optimieren
Keine Kommentare

Dr. Holger Schwichtenberg (MVP) teilt in der Kolumne „.NETversum“ sein Expertenwissen rund um .NET-Tools und WPF mit. In dieser Ausgabe geht es um die Optimierung von Speicheroperationen.

Beim Entity Framework ist die neuere Basisklasse DbContext deutlich langsamer als der ältere ObjectContext – insbesondere gilt das beim Anfügen von Datensätzen.

Der Grund für den Leistungsverlust ist zunächst einmal sehr pauschal erklärbar: Die Klasse DbContext ist ein Wrapper um die Klasse ObjectContext. Dies beweist auch ein Blick in den Quellcode des Entity Frameworks. Ein Wrapper ist nun naturgemäß immer etwas langsamer als seine Innereien.

Speicheroptionen optimieren

Man kann die Anfügeoperationen beim DbContext aber deutlich beschleunigen. Dafür schaltet man die Funktion Auto Detect Changes aus mit der Zeile:

context.Configuration.AutoDetectChangesEnabled = false;

Der DbContext verwendet für das Change Tracking entweder Proxy-Klassen oder Snapshot-Tracking. Ein sich um das Entitätsobjekt legender .NET Runtime Proxy wird automatisch eingesetzt, wenn alle Properties der Entitätsklasse mit virtual deklariert sind. Sind nicht alle Properties der Entitätsklasse mit virtual deklariert, dann verwendet das Entity Framework das „Snapshot Tracking“. Dabei hält der Change Tracker eine Kopie des Ausgangszustands eines jeden Objekts. Das Entity Framework vergleicht dann in der aufwendigen Operation DetectChanges() das alte Objekt mit dem aktuellen Objekt, um die Veränderungen zu erkennen.

DetectChanges() findet aber leider nicht nur vor SaveChanges() statt, sondern wird auch bei vielen anderen Methoden des Entity Framework-API aufgerufen, z. B. bei Find(), Local, Remove(), Add(), Attach(), GetValidationErrors(), Entry(), Entries().

Die Intention von Microsoft für den ständigen Aufruf von DetectChanges() ist gut im Quellcode dokumentiert: Man will erreichen, dass der Entwickler auch stets mit den aktuellen Objektständen arbeitet. DetectChanges() sorgt nicht nur für eine konsistente Sicht des Change Trackers, sondern auch für das Relationship Fixup zwischen Navigationseigenschaften, also die beidseitige Konsistenz bei der Navigation zwischen Objekten. Der automatische Aufruf von DetectChanges() ist also eine Komfortfunktion, zu Gunsten der Entwickler.

Kostenlos: Docker mit .NET auf einen Blick

Container unter Linux und Windows nutzen? Unser Cheatsheet zeigt Ihnen wie Sie: Container starten, analysieren und Docker.DotNet (in C#) verwenden. Jetzt kostenlos herunterladen!

Download for free

Der Snapshot-Tracking-Vergleich findet aber auch schon bei einem Hinzufügen eines Objects zum DbSet<T> mit Add() statt. Das bedeutet: Beim Hinzufügen von 500 Objekten findet der Vergleich ganze 500-mal statt (Listing 1). Und dabei ist er 500-mal völlig überflüssig, denn in der Zwischenzeit wird keine andere Methode des Entity Framework API aufgerufen, die an einem absolut aktuellen Zustand des Change Trackers interessiert sein könnte. Man kann also in Fällen wie dem in Listing 1 dargestellten ohne irgendwelche Nachteile die Option AutoDetectChangesEnabled auf false setzen.

public static void Insert(bool AutoDetectOff)
{
  var db = new ModellDbContext.Modell_WWWings4.WWWings4Entities_DBContext();
  if (AutoDetectOff)
  {
    db.Configuration.AutoDetectChangesEnabled = false;
  }

  for (int f = 1; f <= 500; f++)
  {
    var flug = new ModellDbContext.Modell_WWWings4.Flug();
    flug.FlugNr = f;
    flug.Abflugort = "Abflugort";
    flug.Zielort = "Zielort";
    flug.Plaetze = 250;
    flug.FreiePlaetze = 250;
    flug.Datum = DateTime.Now;
    flug.NichtRaucherFlug = true;
    db.Fluege.Add(flug);
  }
  int count = db.SaveChanges();
  db.Dispose();
}

Dann stellt sich die Frage, ob man AutoDetectChangesEnabled überhaupt in der true-Einstellung braucht. Man betrachte dazu folgenden Dreizeiler:

ctx.Configuration.AutoDetectChangesEnabled = false;

f.FreiePlaetze--;

ctx.SaveChanges();

In diesem Fall würde keine Änderung zur Datenbank gesendet, denn das Entity Framework hat von der Änderung der Platzanzahl nichts mitbekommen. Damit dieser Code dennoch funktioniert, gibt es sechs alternative Lösungen:

  1. Das Change Tracking muss über Runtime Proxies laufen, indem man alle Properties in der Entitätsklasse mit virtual deklariert.
  2. Vor dem Speichern muss man AutoDetectChangesEnabled wieder einschalten.
  3. Vor dem Speichern muss man eine Methode aufrufen, die DetectChanges() auslöst, z. B. Add() in Listing 1.
  4. Vor dem Speichern muss man explizit ChangeTracker.DetectChanges() aufrufen.
  5. Man muss die Änderung über das Entity Framework-API vornehmen:
    Entry(f).Property(p => p.FreiePlaetze).CurrentValue –
  6. Man verwendet zum Anfügen AddRange().

Die einfachste Variante ist sicherlich AddRange(), das es seit Entity Framework 6 gibt. Hierbei muss man AutoDetectChangesEnabled gar nicht vorher ausschalten, um das Problem zu vermeiden. Dies kann man mit dem Code aus Listing 2 testen.

public static void RangeInsert(bool AutoDetectOff = false)
  {
    var db = new ModellDbContext.Modell_WWWings4.WWWings4Entities_DBContext();
    if (AutoDetectOff)
    {
    db.Configuration.AutoDetectChangesEnabled = false;
    }

    var tempListe = new List();

    for (int f = 1; f <= 500; f++)
    {
      var flug = new ModellDbContext.Modell_WWWings4.Flug();
      flug.FlugNr = f;
      flug.Abflugort = "Abflugort";
      flug.Zielort = "Zielort";
      flug.Plaetze = 250;
      flug.FreiePlaetze = 250;
      flug.Datum = DateTime.Now;
      flug.NichtRaucherFlug = true;
      // neue Objekte erst in eine temporäre Liste
      tempListe.Add(flug);
    }

    // Nun die Liste in einem Rutsch anfügen (möglich ab EF 6.0)
    db.Fluege.AddRange(tempListe);

    int count = db.SaveChanges();

    db.Dispose();
  }

„InnerException“-Ausgabe ohne Stacktrace

Bei der Ausgabe eines Exception-Objekts bekommt man viele Informationen über die Fehlerklasse und auch die inneren Fehler (InnerException): Klassenname, Fehlertext (Message) und Stacktrace. Das ist aber manchmal etwas zu viel des Guten, denn der Stacktrace kann sehr lang sein.
Die folgende Erweiterungsmethode für die Klasse System.Exception gibt im Standard nur die Fehlerklasse und den Fehlertext aus. Mit dem Parameter verbose kann man das Stacktrace optional mit ausgeben. Zudem kann man die verbose-Einstellung global setzen und im Aufruf der Methode diese Einstellung wieder außer Kraft setzen.

/// <summary>
///  Erweiterungen der Klasse Exception
/// </summary>
public static class ExceptionExtensions
{
  public static bool Verbose = false;

  /// <summary>
/// Liefert Nachricht der Exception inkl. InnerException, aber optional ohne StackTrace!
/// </summary>
/// <param name="ex">Fehlerklasse</param>
/// <param name="verbose">Steuert, ob StackTrace ausgegeben wird</param>
/// <param name="forceNotVerbose">Übersteuert die globale Verbose-Einstellung</param>
/// <returns></returns>
public static string GetFullMessage(this Exception ex, bool verbose = false, bool forceNotVerbose = false, string separator = "\n")
{
  string ausgabe = ex.GetType().FullName + ": " + ex.Message + separator;
  if (ex.InnerException != null) ausgabe += GetFullMessage(ex.InnerException);
  if ((Verbose || verbose) && !forceNotVerbose) ausgabe += separator + ex.StackTrace;
  return ausgabe;
}
}

Listing 4 zeigt ein Anwendungsbeispiel, das GetFullMessage() im Vergleich zu ToString() zeigt.

private static void Demo_ExceptionAusgabe()
{
  try
  {
    MacheEinenFehler();
  }
  catch (Exception ex2)
  {
    CUI.Print("ToString()", ConsoleColor.Yellow);
    Console.WriteLine(ex2.ToString());
    CUI.Print("GetFullMessage()", ConsoleColor.Yellow);
    Console.WriteLine(ex2.GetFullMessage());
  }
}

private static void MacheEinenFehler()
{
  var ex1 = new ApplicationException("Berechnung nicht möglich",
    new InvalidOperationException("Ungültige Eingabedaten", new ArgumentNullException("Eingabewert")));
  throw ex1;
}

Abbildung 1 zeigt die Ausgabe des Beispiels.

Abb. 1: Ergebnisdarstellung

Buchempfehlungdotnet_praxis

In den vergangenen Jahren haben Dr. Holger Schwichtenberg und Manfred Steyer im „Windows Developer“ regelmäßig Tipps und Tricks aus ihrem Alltag als Softwareentwickler, Berater und Trainer für .NET- und webbasierte Anwendungen veröffentlicht. Das vorliegende Buch ist eine aktualisierte und deutlich erweiterte Zusammenfassung dieser Tipps und Tricks. Es umfasst sowohl das .NET Framework und seine zahlreichen Teilbibliotheken als auch die beliebte JavaScript-Bibliothek AngularJS sowie die Entwicklungsumgebung Visual Studio und ergänzende Werkzeuge.

Mehr Infos: www.entwickler-press.de/Dotnet-Praxis

Windows Developer

Windows DeveloperDieser Artikel ist im Windows Developer erschienen. Windows Developer informiert umfassend und herstellerneutral über neue Trends und Möglichkeiten der Software- und Systementwicklung rund um Microsoft-Technologien.

Natürlich können Sie den Windows Developer über den entwickler.kiosk auch digital im Browser oder auf Ihren Android- und iOS-Devices lesen. In unserem Shop ist der Windows Developer ferner im Abonnement oder als Einzelheft erhältlich.

Unsere Redaktion empfiehlt:

Relevante Beiträge

X
- Gib Deinen Standort ein -
- or -