Eine der zentralen Funktionen im ADO.NET-Entity-Framework ist die Änderungsverfolgung (Change Tracking) der geladenen Objekte. Ein SaveChange() auf dem Entity-Framework-Kontextobjekt (ObjectContext oder DbContext) speichert alle Änderungen seit dem Laden der Objekte oder dem letzten Speichern zurück in die Datenbank. Die Änderungsverfolgung im so genannten Objektzustandsmanager wirkt bis zur Attributebene, das heißt für eine Änderung an zwei Attributen in einem Objekt mit zehn Attributen gibt es nur zwei SET-Aktionen im Rahmen der SQL-UPDATE-Anweisung auf genau die beiden Spalten.
Die Änderungsverfolgung ist zwar sehr elegant, aber verbraucht auch Ressourcen und Rechenzeit. So dauert in einem konkreten Fall bei uns das reine Einlesen von allen 10 000 Datensätzen aus einer Tabelle mit Änderungsverfolgung 118 Millisekunden und ohne 74 Millisekunden. Ein anderer Fall bei uns macht es noch deutlicher: Das Filtern von 284 448 Datensätzen aus einer Tabelle mit 776 Millionen Datensätzen dauert mit Änderungsverfolgung 16,9 Sekunden, ohne nur 2,9 Sekunden (Abb.). Es lohnt sich also, die Änderungsverfolgung abzuschalten. Die Abschaltung der Änderungsverfolgung ist eine Option unter System.Data.Objects.MergeOption. Eine MergeOption kann man an folgenden Stellen angeben:
- bei dem Entity Framework-Kontext für komplette Mengen:
db.Flug.MergeOption = MergeOption.NoTracking;
- bei LINQ-Abfragen durch Typkonvertierung auf ObjectQuery:
var FlugQuery = (from x in db.Flug select x).OrderBy(f4 => f4.FlugNr).Skip(10);
(FlugQuery as ObjectQuery).MergeOption = MergeOption.NoTracking;
- bei LINQ-Abfragen ab Entity-Framework 4.1 bei Verwendung von DbContext statt ObjectContext durch den NoTracking-Operator:
var FlugQuery = (from x in db.Flug.AsNoTracking() select x).OrderBy(f4 => f4.FlugNr).Skip(10);
- bei SQL-Abfragen als Parameter bei ExecuteStoreQuery() oder Translate():
var modell4 = new Modelle.EF6.WWWings6Entities();
var q4 = modell4.ExecuteStoreQuery
{0} and Zielort = {1}“, „Flug“,
System.Data.Objects.MergeOption.NoTracking, „Rom“, „Paris“);
- beim Aufruf von gespeicherten Prozeduren muss man leider einen Umweg über ExecuteFunction() gehen. Beim typisierten Aufruf hat Microsoft leider keine Option für die Angabe der MergeOption vorgesehen:
ObjectParameter[] parameter = { new ObjectParameter(„von“, „Rom“),
new ObjectParameter(„nach“, „Paris“) };
var q5 = modell1.ExecuteFunction
Die über NoTracking geladenen Objekte sind normale Instanzen der Entitätsklassen. Man kann sogar schreibend auf sie zugreifen. Aber das Entity-Framework ignoriert bei SaveChanges() alle diese Änderungen:
Console.WriteLine(q5.Count);
q5[0].FreiePlaetze–;
var e5 = modell2.SaveChanges();
Console.WriteLine(e5); // muss 0 ergeben
if (e5 > 0) Debugger.Break();
Dass das Entity-Framework die mit NoTracking geladenen Objekte nicht zurückspeichern kann, erkennt man daran, dass sie im Zustand detached sind, während normale Entity-Framework-Objekte nach dem Laden im Zustand unchanged sind. Diesen Umstand kann aber noch nachträglich ändern, indem man mit Attach() ein Objekt, das man ändern möchte, dem Kontext hinzufügt. Im Idealfall führt man vor der Änderung Attach() aus, dann merkt der Objektzustandsmanager des Entity-Frameworks sich genau, was sich am Objekt geändert hat (Listing 1). Hieraus entsteht folgender UPDATE-Befehl über die eine Spalte:
exec sp_executesql N’update [dbo].[Flug]
set [FreiePlaetze] = @0
where ([FlugNr] = @1)
‚,N’@0 smallint,@1 int‘,@0=112,@1=145
Alternativ kann man Attach() auch nach der Änderung aufrufen und dem Objektzustandsmanager des Entity-Frameworks dann manuell mitteilen, dass das Objekt sich geändert hat (Listing 2). Allerdings weiß der Objektzustandsmanager dann nicht, welche Attribute im Objekt geändert wurden. Er wird also im UPDATE-Befehl alle Spalten neu setzen (Listing 3).
Listing 1
// Heilen des NoTracking-/Detached-Zustandes: Version A modell1.Flug.Attach(f5); f5.FreiePlaetze--; Console.WriteLine(f5.EntityState); var e6 = modell1.SaveChanges(); Console.WriteLine(e6); // muss 1 ergeben if (e6 != 1) Debugger.Break();
Listing 2
// Heilen des NoTracking-/Detached-Zustandes: Version B f5.FreiePlaetze--; modell1.Flug.Attach(f5); modell1.ObjectStateManager.ChangeObjectState(f5, EntityState.Modified); Console.WriteLine(f5.EntityState); var e6 = modell1.SaveChanges(); Console.WriteLine(e6); // muss 1 ergeben if (e6 != 1) Debugger.Break();
Listing 3
exec sp_executesql N'update [dbo].[Flug] set [Abflugort] = @0, [Zielort] = @1, [Datum] = @2, [NichtRaucherFlug] = @3, [Plaetze] = @4, [FreiePlaetze] = @5, [Pilot_PersonID] = null, [Ankunft] = null, [Memo] = null where ([FlugNr] = @6) ',N'@0 nvarchar(20),@1 nvarchar(20),@2 datetime2(7),@3 bit,@4 smallint,@5 smallint,@6 int',@0=N'Rom',@1=N'Paris',@2='2011-08-03 07:16:59.8130000',@3=0,@4=0,@5=114,@6=145
