C # Entity Framework: Problem mit dem Eingangsspeicher für Massenerweiterungen
Ich verwende derzeit EF-Erweiterungen. Eine Sache verstehe ich nicht, "es soll bei der Leistung helfen"
Das Platzieren von mehr als einer Million Datensätzen in der List-Variablen ist jedoch selbst ein Speicherproblem. Wie kann dies effizient durchgeführt werden, wenn Millionen Datensätze aktualisiert werden sollen, ohne alles im Speicher zu halten?
Sollten wir ein verwenden for loopund in Stapeln aktualisieren, sagen wir 10.000? Verfügt EFExtensions BulkUpdate über native Funktionen, um dies zu unterstützen?
Beispiel:
var productUpdate = _dbContext.Set<Product>()
.Where(x => x.ProductType == 'Electronics'); // this creates IQueryable
await productUpdate.ForEachAsync(c => c.ProductBrand = 'ABC Company');
_dbContext.BulkUpdateAsync(productUpdate.ToList());
Ressource:
https://entityframework-extensions.net/bulk-update
Antworten
Ich habe die "richtige" Möglichkeit für EF-Erweiterungen gefunden, ein Massenupdate mit einer abfrageähnlichen Bedingung durchzuführen:
var productUpdate = _dbContext.Set<Product>()
.Where(x => x.ProductType == 'Electronics')
.UpdateFromQuery( x => new Product { ProductBrand = "ABC Company" });
Dies sollte zu einer ordnungsgemäßen SQL führen UPDATE ... SET ... WHERE, ohne dass zuerst Entitäten gemäß der Dokumentation geladen werden müssen :
Warum
UpdateFromQueryist schneller alsSaveChanges,BulkSaveChangesundBulkUpdate?
UpdateFromQueryführt eine Anweisung direkt in SQL aus, zUPDATE [TableName] SET [SetColumnsAndValues] WHERE [Key].Andere Vorgänge erfordern normalerweise einen oder mehrere Datenbank-Roundtrips, wodurch die Leistung langsamer wird.
Sie können die funktionierende Syntax in diesem Dotnet-Geigenbeispiel überprüfen, das an das Beispiel von angepasst wurde BulkUpdate.
Andere Überlegungen
Leider werden hierfür keine Batch-Operationen erwähnt.
Bevor Sie ein großes Update wie dieses durchführen, sollten Sie in Betracht ziehen, die Indizes für diese Spalte zu deaktivieren und sie anschließend neu zu erstellen. Dies ist besonders nützlich, wenn Sie viele davon haben.
Vorsicht bei der Bedingung in der
Where, wenn es von EF nicht als SQL übersetzt werden kann, dann wird es clientseitig durchgeführt, was den "üblichen" schrecklichen Roundtrip "Laden - Ändern des Speichers - Aktualisieren" bedeutet.
Dies ist eigentlich etwas, für das EF nicht gemacht ist. Die Datenbankinteraktionen von EF beginnen mit dem Datensatzobjekt und fließen von dort aus. EF kann kein partielles UPDATE generieren (dh nicht alles überschreiben), wenn die Entität nicht nachverfolgt (und daher geladen) wurde, und kann auch keine Datensätze basierend auf einer Bedingung anstelle eines Schlüssels LÖSCHEN.
Es gibt kein EF-Äquivalent (ohne alle diese Datensätze zu laden) für die bedingte Aktualisierungs- / Löschlogik wie z
UPDATE People
SET FirstName = 'Bob'
WHERE FirstName = 'Robert'
oder
DELETE FROM People
WHERE FirstName = 'Robert'
Um dies mit dem EF-Ansatz zu tun, müssen Sie alle diese Entitäten laden, um sie (mit einem Update oder Löschen) an die Datenbank zurückzusenden. Dies ist eine Verschwendung von Bandbreite und Leistung, wie Sie bereits festgestellt haben.
Die beste Lösung, die ich hier gefunden habe, besteht darin, die LINQ-freundlichen Methoden von EF zu umgehen und stattdessen das unformatierte SQL selbst auszuführen. Dies kann weiterhin in einem EF-Kontext erfolgen.
using (var ctx = new MyContext())
{
string updateCommand = "UPDATE People SET FirstName = 'Bob' WHERE FirstName = 'Robert'";
int noOfRowsUpdated = ctx.Database.ExecuteSqlCommand(updateCommand);
string deleteCommand = "DELETE FROM People WHERE FirstName = 'Robert'";
int noOfRowsDeleted = ctx.Database.ExecuteSqlCommand(deleteCommand);
}
Weitere Informationen hier . Vergessen Sie natürlich nicht, gegebenenfalls vor SQL-Injection zu schützen .
Die spezifische Syntax zum Ausführen von Raw-SQL kann je nach Version von EF / EF Core variieren. Soweit mir bekannt ist, können Sie in allen Versionen Raw-SQL ausführen.
Ich kann die Leistung von EF Extensions oder BulkUpdate nicht speziell kommentieren und werde sie nicht bei ihnen kaufen.
Aufgrund ihrer Dokumentation scheinen sie nicht über die Methoden mit den richtigen Signaturen zu verfügen, um eine bedingte Aktualisierungs- / Löschlogik zu ermöglichen.
BulkUpdateEs scheint Ihnen nicht möglich zu sein, die logische Bedingung (das WHERE in Ihrem UPDATE-Befehl) einzugeben, mit der Sie dies optimieren können.BulkDeletehat immer noch eineBatchSizeEinstellung, die darauf hindeutet, dass sie die Datensätze immer noch einzeln verarbeiten (naja, pro Stapel, denke ich) und keine einzige DELETE-Abfrage mit einer Bedingung verwenden (WHERE-Klausel).
Basierend auf Ihrem beabsichtigten Code in der Frage bietet Ihnen EF Extensions nicht wirklich das, was Sie benötigen. Es ist leistungsfähiger und billiger, einfach unformatiertes SQL in der Datenbank auszuführen, da dies die Notwendigkeit von EF umgeht, seine Entitäten zu laden.
Update
Ich könnte korrigiert stehen, es gibt einige Unterstützung für bedingte Update-Logik, wie hier zu sehen . Es ist mir jedoch unklar, während das Beispiel noch alles in den Speicher lädt und was der Zweck dieser bedingten WHERE-Logik ist, wenn Sie bereits alles in den Speicher geladen haben (warum dann nicht LINQ im Speicher verwenden?).
Selbst wenn dies ohne Laden der Entitäten funktioniert, ist es dennoch:
- eingeschränkter (nur Gleichheitsprüfungen sind zulässig, im Vergleich zu SQL, das eine boolesche Bedingung zulässt, die gültiges SQL ist),
- relativ komplex (Ich mag ihre Syntax nicht, vielleicht ist das subjektiv)
- und teurer (immer noch eine kostenpflichtige Bibliothek)
im Vergleich zum Rollen Ihrer eigenen SQL-Rohabfrage. Ich würde immer noch vorschlagen, hier Ihr eigenes Raw-SQL zu rollen, aber das ist nur meine Meinung.