Entity Framework
Optimierungstechniken in EF
Suche…
AsNoTracking verwenden
Schlechtes Beispiel:
var location = dbContext.Location
.Where(l => l.Location.ID == location_ID)
.SingleOrDefault();
return location;
Da der obige Code lediglich eine Entität zurückgibt, ohne sie zu ändern oder hinzuzufügen, können wir die Kosten für die Nachverfolgung vermeiden.
Gutes Beispiel:
var location = dbContext.Location.AsNoTracking()
.Where(l => l.Location.ID == location_ID)
.SingleOrDefault();
return location;
Wenn wir die Funktion AsNoTracking()
verwenden, AsNoTracking()
wir Entity Framework explizit mit, dass die Entitäten nicht durch den Kontext verfolgt werden. Dies kann besonders nützlich sein, wenn große Datenmengen aus Ihrem Datenspeicher abgerufen werden. Wenn Sie jedoch Änderungen an nicht nachverfolgten Entitäten vornehmen möchten, müssen Sie diese vor dem Aufruf von SaveChanges
.
Nur benötigte Daten werden geladen
Ein häufig beim Code auftretendes Problem ist das Laden aller Daten. Dadurch wird die Belastung des Servers erheblich erhöht.
Angenommen, ich habe ein Modell mit dem Namen "location", das 10 Felder enthält, aber nicht alle Felder werden gleichzeitig benötigt. Angenommen, ich möchte nur den Parameter 'LocationName' dieses Modells.
Schlechtes Beispiel
var location = dbContext.Location.AsNoTracking()
.Where(l => l.Location.ID == location_ID)
.SingleOrDefault();
return location.Name;
Gutes Beispiel
var location = dbContext.Location
.Where(l => l.Location.ID == location_ID)
.Select(l => l.LocationName);
.SingleOrDefault();
return location;
Der Code im "guten Beispiel" wird nur "LocationName" und nichts anderes abrufen.
Beachten Sie, dass AsNoTracking()
nicht erforderlich ist, da in diesem Beispiel keine Entität AsNoTracking()
wird. Es gibt sowieso nichts zu verfolgen.
Weitere Felder mit anonymen Typen abrufen
var location = dbContext.Location
.Where(l => l.Location.ID == location_ID)
.Select(l => new { Name = l.LocationName, Area = l.LocationArea })
.SingleOrDefault();
return location.Name + " has an area of " + location.Area;
Wie im vorherigen Beispiel werden nur die Felder 'LocationName' und 'LocationArea' aus der Datenbank abgerufen. Der anonyme Typ kann so viele Werte enthalten, wie Sie möchten.
Führen Sie, wenn möglich, Abfragen in der Datenbank aus, nicht im Speicher.
Angenommen, wir wollen zählen, wie viele Grafschaften es in Texas gibt:
var counties = dbContext.States.Single(s => s.Code == "tx").Counties.Count();
Die Abfrage ist korrekt, aber ineffizient. States.Single(…)
lädt einen Status aus der Datenbank. Als Nächstes lädt Counties
alle 254 Counties mit allen Feldern in einer zweiten Abfrage. .Count()
wird dann in der geladenen Counties
Sammlung im Speicher ausgeführt .
Wir haben viele Daten geladen, die wir nicht benötigen, und wir können es besser machen:
var counties = dbContext.Counties.Count(c => c.State.Code == "tx");
Hier führen wir nur eine Abfrage aus, die in SQL in eine Anzahl und einen Join übersetzt wird. Wir geben nur die Anzahl aus der Datenbank zurück - wir haben zurückgegebene Zeilen, Felder und die Erstellung von Objekten gespeichert.
Der Abfragetyp lässt sich anhand des Auflistungstyps leicht erkennen: IQueryable<T>
vs. IEnumerable<T>
.
Führen Sie mehrere Abfragen asynchron und parallel aus
Wenn Sie asynchrone Abfragen verwenden, können Sie mehrere Abfragen gleichzeitig ausführen, jedoch nicht in demselben Kontext. Wenn die Ausführungszeit einer Abfrage 10 Sekunden beträgt, beträgt die Zeit für das fehlerhafte Beispiel 20 Sekunden, während die Zeit für das gute Beispiel 10 Sekunden beträgt.
Schlechtes Beispiel
IEnumerable<TResult1> result1;
IEnumerable<TResult2> result2;
using(var context = new Context())
{
result1 = await context.Set<TResult1>().ToListAsync().ConfigureAwait(false);
result2 = await context.Set<TResult1>().ToListAsync().ConfigureAwait(false);
}
Gutes Beispiel
public async Task<IEnumerable<TResult>> GetResult<TResult>()
{
using(var context = new Context())
{
return await context.Set<TResult1>().ToListAsync().ConfigureAwait(false);
}
}
IEnumerable<TResult1> result1;
IEnumerable<TResult2> result2;
var result1Task = GetResult<TResult1>();
var result2Task = GetResult<TResult2>();
await Task.WhenAll(result1Task, result2Task).ConfigureAwait(false);
var result1 = result1Task.Result;
var result2 = result2Task.Result;
Deaktivieren Sie die Änderungsnachverfolgung und die Proxy-Generierung
Wenn Sie nur Daten abrufen möchten, aber nichts ändern möchten, können Sie die Änderungsnachverfolgung und die Proxy-Erstellung deaktivieren. Dies verbessert Ihre Leistung und verhindert auch ein verzögertes Laden.
Schlechtes Beispiel:
using(var context = new Context())
{
return await context.Set<MyEntity>().ToListAsync().ConfigureAwait(false);
}
Gutes Beispiel:
using(var context = new Context())
{
context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ProxyCreationEnabled = false;
return await context.Set<MyEntity>().ToListAsync().ConfigureAwait(false);
}
Es ist besonders üblich, diese Einstellungen im Konstruktor Ihres Kontexts zu deaktivieren, insbesondere wenn Sie möchten, dass diese in Ihrer Lösung angezeigt werden:
public class MyContext : DbContext
{
public MyContext()
: base("MyContext")
{
Configuration.AutoDetectChangesEnabled = false;
Configuration.ProxyCreationEnabled = false;
}
//snip
}
Mit Stub-Entitäten arbeiten
Nehmen wir an, wir haben Product
und Category
in einer Viele-zu-Viele-Beziehung:
public class Product
{
public Product()
{
Categories = new HashSet<Category>();
}
public int ProductId { get; set; }
public string ProductName { get; set; }
public virtual ICollection<Category> Categories { get; private set; }
}
public class Category
{
public Category()
{
Products = new HashSet<Product>();
}
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
Wenn Sie einem Product
eine Category
hinzufügen möchten, müssen Sie das Produkt laden und die Kategorie zu seinen Categories
hinzufügen. Beispiel:
Schlechtes Beispiel:
var product = db.Products.Find(1);
var category = db.Categories.Find(2);
product.Categories.Add(category);
db.SaveChanges();
(wobei db
eine DbContext
Unterklasse ist).
Dadurch wird ein Datensatz in der Junction-Tabelle zwischen Product
und Category
. Diese Tabelle enthält jedoch nur zwei Id
Werte. Es ist eine Verschwendung von Ressourcen, zwei vollständige Entitäten zu laden, um einen kleinen Datensatz zu erstellen.
Eine effizientere Methode ist die Verwendung von Stub-Entities , dh Entity-Objekten, die im Speicher erstellt werden und nur das absolute Minimum an Daten enthalten, normalerweise nur einen Id
Wert. So sieht es aus:
Gutes Beispiel:
// Create two stub entities
var product = new Product { ProductId = 1 };
var category = new Category { CategoryId = 2 };
// Attach the stub entities to the context
db.Entry(product).State = System.Data.Entity.EntityState.Unchanged;
db.Entry(category).State = System.Data.Entity.EntityState.Unchanged;
product.Categories.Add(category);
db.SaveChanges();
Das Endergebnis ist dasselbe, aber es werden zwei Roundtrips zur Datenbank vermieden.
Duplikate vermeiden
Wenn Sie prüfen möchten, ob die Verknüpfung bereits vorhanden ist, genügt eine billige Abfrage. Zum Beispiel:
var exists = db.Categories.Any(c => c.Id == 1 && c.Products.Any(p => p.Id == 14));
Auch hier werden keine vollständigen Elemente in den Speicher geladen. Es fragt effektiv die Junction-Tabelle ab und gibt nur einen Boolean zurück.