Wie lösche ich verfolgte Entitäten im Entitätsframework?

81

Ich führe einen Korrekturcode aus, der über einen großen Stapel von Entitäten läuft, während seine Geschwindigkeit abnimmt. Dies liegt daran, dass die Anzahl der verfolgten Entitäten im Kontext mit jeder Iteration zunimmt. Es kann lange dauern, sodass ich am Ende Änderungen speichere jeder Iteration. Jede Iteration ist unabhängig und ändert die zuvor geladenen Entitäten nicht.

Ich weiß, dass ich die Änderungsverfolgung deaktivieren kann, möchte dies aber nicht, da es sich nicht um einen Masseneinfügecode handelt, sondern um das Laden der Entitäten und das Berechnen einiger Dinge. Wenn die Zahlen nicht korrekt sind, setzen Sie die neuen Zahlen und aktualisieren / löschen / erstellen einige zusätzliche Entitäten. Ich weiß, dass ich für jede Iteration einen neuen DbContext erstellen kann, und das würde wahrscheinlich schneller laufen als alle in derselben Instanz, aber ich denke, dass es einen besseren Weg geben könnte.

Die Frage ist also: Gibt es eine Möglichkeit, die zuvor im Datenbankkontext geladenen Entitäten zu löschen?

Hazimdikenli
quelle
5
Sie können einfach anrufen context.Entry(entity).State = EntityState.Detachedund die Verfolgung dieser bestimmten Entität wird beendet.
Ben Robinson
2
Warum instanziieren Sie nicht einfach einen neuen Kontext? Es gibt wirklich keinen großen Overhead, es sei denn, Sie benötigen sehr optimierten Code.
Adrian Nasui
Das Entity Framework trifft den Datenbankserver nur für die geänderten Entitäten. Sie haben keine Leistungsbedenken. Sie können jedoch einen neuen Kontext erstellen, der nur aus den Tabellen besteht, mit denen Sie arbeiten, um ihn schneller zu machen.
İsmet Alkan
1
@IsThatSo das Erkennen der Änderungen braucht Zeit, ich mache mir keine Sorgen um DbPerformance.
Hazimdikenli
Haben Sie den Leistungsengpass tatsächlich getestet und nachverfolgt oder nur angenommen?
İsmet Alkan

Antworten:

117

Sie können Ihrer DbContextoder einer Erweiterungsmethode eine Methode hinzufügen , die den ChangeTracker verwendet, um alle hinzugefügten, geänderten und gelöschten Entitäten zu trennen:

public void DetachAllEntities()
{
    var changedEntriesCopy = this.ChangeTracker.Entries()
        .Where(e => e.State == EntityState.Added ||
                    e.State == EntityState.Modified ||
                    e.State == EntityState.Deleted)
        .ToList();

    foreach (var entry in changedEntriesCopy)
        entry.State = EntityState.Detached;
}
David Sherret
quelle
5
Stellen Sie sicher, dass Sie 'ToList' nach dem 'Where' aufrufen. Andernfalls wird eine System.InvalidOperationException ausgelöst: 'Sammlung wurde geändert; Aufzählungsoperation wird möglicherweise nicht ausgeführt. '
Mabead
6
In meinem Komponententest lautet der Eintragsstatus "Nicht geändert", möglicherweise weil ich eine Transaktion verwende, die ich am Ende der Testmethode zurücksetze. Dies bedeutete, dass ich den Status der verfolgten Einträge auf "Getrennt" setzen musste, ohne den aktuellen Status zu überprüfen, damit meine Tests alle auf einmal korrekt ausgeführt wurden. Ich rufe den obigen Code direkt nach dem Zurücksetzen der Transaktion auf, aber ich habe ihn erhalten. Das Zurücksetzen bedeutet sicherlich einen unveränderten Status.
barbara.post
2
(und var entitysollte auch wirklich sein, var entryda es der Eintrag ist, nicht die tatsächliche Entität)
Oatsoda
2
@ DavidSherret Dachte, das könnte der Fall sein! Ich habe dies festgestellt, weil in einer meiner Test-Apps das Durchlaufen von 1000 Elementen und das Markieren als getrennt mit dem vorhandenen Code ca. 6000 ms dauerte. Über 15ms mit dem neuen :)
Oatsoda
3
Sollten Sie nicht auch e.State == EntityState.Unchanged verwenden? Obwohl die Entität unverändert bleibt, wird sie dennoch im Kontext verfolgt und ist Teil einer Reihe von Entitäten, die bei DetectChanges berücksichtigt werden. Beispiel: Sie fügen eine neue Entität hinzu (sie befindet sich im Status Hinzugefügt), rufen SaveChanges auf und die hinzugefügte Entität hat jetzt den Status Unverändert (dies widerspricht dem UnitOfWork-Muster, wird jedoch gefragt: Ich speichere Änderungen am Ende jeder Iteration ).
Jahav
25

1. Möglichkeit: Trennen Sie den Eintrag

dbContext.Entry(entity).State = EntityState.Detached;

Wenn Sie den Eintrag trennen, beendet der Änderungs-Tracker die Verfolgung (und sollte zu einer besseren Leistung führen).

Siehe: http://msdn.microsoft.com/de-de/library/system.data.entitystate(v=vs.110).aspx

2. Möglichkeit: Arbeiten Sie mit Ihrem eigenen StatusFeld + getrennten Kontexten

Möglicherweise möchten Sie den Status Ihrer Entität unabhängig steuern, damit Sie nicht verbundene Diagramme verwenden können. Fügen Sie eine Eigenschaft für den Entitätsstatus hinzu und wandeln Sie diesen Status in den Status um, dbContext.Entry(entity).Statewenn Sie Vorgänge ausführen (verwenden Sie dazu ein Repository).

public class Foo
{
    public EntityStatus EntityStatus { get; set; }
}

public enum EntityStatus
{
    Unmodified,
    Modified,
    Added
}

Ein Beispiel finden Sie unter folgendem Link: https://www.safaribooksonline.com/library/view/programming-entity-framework/9781449331825/ch04s06.html

road242
quelle
Ich denke, das Hinzufügen einer Erweiterungsmethode und das Ausführen aller Entitäten im ChangeTracker und das Trennen dieser Entitäten sollten funktionieren.
Hazimdikenli
15

Ich führe einen Windows-Dienst aus, der die Werte jede Minute aktualisiert, und ich hatte das gleiche Problem. Ich habe versucht, die @ DavidSherrets-Lösung auszuführen, aber nach einigen Stunden wurde dies auch langsam. Meine Lösung bestand darin, einfach für jeden neuen Lauf einen neuen Kontext wie diesen zu erstellen. Einfach, aber es funktioniert.

_dbContext = new DbContext();

Ogglas
quelle
5
Dies ist keine "Einfache, aber funktionierende" Lösung für Ihr Ziel. Es ist das einzig Richtige. Der Kontext sollte so wenig wie möglich leben. 1 Kontext pro 1 Transaktion ist die beste Option.
Jegor Androsov
2
In Übereinstimmung mit @pwrigshihanomoronimo folgt der Kontext dem UnitOfWork-Entwurfsmuster. Wie von Martin Fowler definiert:> Verwaltet eine Liste der von einem Geschäftsvorfall betroffenen Objekte und> koordiniert das Ausschreiben von Änderungen und die Lösung von Parallelitätsproblemen>.
Michel
Dies schien den Trick für mich zu tun. Ich synchronisiere Daten und habe ungefähr eine halbe Million Transaktionen (Einfügen und Aktualisieren) in Tabellen mit ein paar Millionen Zeilen. Also hatte ich nach einer Weile (oder einer Reihe von Operationen) Probleme mit OutOfMemoryException. Dies wurde behoben, als ich einen neuen DbContext pro X Anzahl von Schleifen erstellte, der ein natürlicher Ort war, um den Kontext erneut zu instanziieren. Dies hat GC wahrscheinlich besser ausgelöst, da nicht über den möglichen Speicherverlust bei EF und lang laufende Vorgänge nachgedacht werden musste. Vielen Dank!
Mats Magnem
4

Ich bin gerade auf dieses Problem gestoßen und bin schließlich auf eine bessere Lösung für diejenigen gestoßen, die die typische .NET Core-Abhängigkeitsinjektion verwenden. Sie können für jede Operation einen DbContext mit Gültigkeitsbereich verwenden. Das wird zurückgesetzt, DbContext.ChangeTrackerdamit SaveChangesAsync()die Überprüfung von Entitäten aus früheren Iterationen nicht ins Stocken gerät. Hier ist ein Beispiel für eine ASP.NET Core Controller-Methode:

    /// <summary>
    /// An endpoint that processes a batch of records.
    /// </summary>
    /// <param name="provider">The service provider to create scoped DbContexts.
    /// This is injected by DI per the FromServices attribute.</param>
    /// <param name="records">The batch of records.</param>
    public async Task<IActionResult> PostRecords(
        [FromServices] IServiceProvider provider,
        Record[] records)
    {
        // The service scope factory is used to create a scope per iteration
        var serviceScopeFactory =
            provider.GetRequiredService<IServiceScopeFactory>();

        foreach (var record in records)
        {
            // At the end of the using block, scope.Dispose() will be called,
            // release the DbContext so it can be disposed/reset
            using (var scope = serviceScopeFactory.CreateScope())
            {
                var context = scope.ServiceProvider.GetService<MainDbContext>();

                // Query and modify database records as needed

                await context.SaveChangesAsync();
            }
        }

        return Ok();
    }

Da ASP.NET Core-Projekte normalerweise DbContextPool verwenden, werden die DbContext-Objekte nicht einmal erstellt / zerstört. (Falls Sie interessiert waren, ruft DbContextPool tatsächlich auf DbContext.ResetState()und DbContext.Resurrect(), aber ich würde nicht empfehlen, diese direkt aus Ihrem Code heraus aufzurufen, da sie sich wahrscheinlich in zukünftigen Versionen ändern werden.) Https://github.com/aspnet/EntityFrameworkCore/blob/v2 .2.1 / src / EFCore / Internal / DbContextPool.cs # L157

Matt
quelle
0

Ab EF Core 3.0 gibt es eine interne API , die den ChangeTracker zurücksetzen kann. Verwenden Sie dies nicht im Produktionscode, ich erwähne es, da es je nach Szenario jemandem beim Testen helfen kann.

using Microsoft.EntityFrameworkCore.Internal;

_context.GetDependencies().StateManager.ResetState();

Wie der Kommentar zum Code sagt;

Dies ist eine interne API, die die Entity Framework Core-Infrastruktur unterstützt und nicht denselben Kompatibilitätsstandards wie öffentliche APIs unterliegt. Es kann ohne vorherige Ankündigung in jeder Version geändert oder entfernt werden. Sie sollten es nur mit äußerster Vorsicht direkt in Ihrem Code verwenden und wissen, dass dies zu Anwendungsfehlern führen kann, wenn Sie auf eine neue Entity Framework Core-Version aktualisieren.

Stuart Hallows
quelle
-3

Nun, meine Meinung ist, dass EF oder ein Orm meiner Erfahrung nach unter zu viel Druck oder komplexem Modell nicht gut funktioniert.

Wenn du nicht verfolgen willst, würde ich wirklich sagen, warum überhaupt orm?

Wenn Geschwindigkeit die Hauptkraft ist, geht nichts über gespeicherte Prozeduren und eine gute Indizierung.

Und darüber hinaus, wenn Ihre Abfragen immer pro ID sind, sollten Sie eine nosql oder vielleicht sql mit nur key und json verwenden. Dies würde das Impedanzproblem zwischen Klassen und Tabellen vermeiden.

In Ihrem Fall erscheint mir das Laden von Objekten in Objekte auf diese Weise sehr langsam. In Ihrem Fall sind gespeicherte Prozeduren wirklich besser, da Sie den Datentransport durch das Netzwerk vermeiden und SQL viel schneller und optimierter ist, um die Aggregation und ähnliche Dinge zu verwalten.

Kat Lim Ruiz
quelle