Entity Framework Core durchläuft große Blob-Daten ohne Speicherüberlauf, Best Practice

8

Ich schreibe Code, der große Mengen von Bilddaten durchläuft, und bereite einen großen Delta-Block vor, der alles zum Senden komprimiert enthält.

Hier ist ein Beispiel, wie diese Daten sein könnten

[MessagePackObject]
public class Blob : VersionEntity
{
    [Key(2)]
    public Guid Id { get; set; }
    [Key(3)]
    public DateTime CreatedAt { get; set; }
    [Key(4)]
    public string Mediatype { get; set; }
    [Key(5)]
    public string Filename { get; set; }
    [Key(6)]
    public string Comment { get; set; }
    [Key(7)]
    public byte[] Data { get; set; }
    [Key(8)]
    public bool IsTemporarySmall { get; set; }
}

public class BlobDbContext : DbContext
{
    public DbSet<Blob> Blob { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blob>().HasKey(o => o.Id);
    }
}

Wenn ich damit arbeite, verarbeite ich alles zu einem Dateistream und möchte zu einem bestimmten Zeitpunkt so wenig wie möglich im Speicher behalten.

Ist es genug, um es so zu machen?

foreach(var b in context.Where(o => somefilters).AsNoTracking())
    MessagePackSerializer.Serialize(stream, b);

Füllt dies immer noch den Speicher mit allen Blob-Datensätzen oder werden sie einzeln verarbeitet, während ich den Enumerator durchlaufe. Es wird keine ToList verwendet, nur der Enumerator, daher sollte Entity Framework in der Lage sein, sie unterwegs zu verarbeiten, aber ich bin mir nicht sicher, ob dies der Fall ist.

Alle Entity Framework-Experten hier, die eine Anleitung geben können, wie dies richtig gehandhabt wird.

Atle S.
quelle
Ich bin nicht 100% sicher, aber ich denke, dies wird dazu führen, dass eine einzelne Abfrage an die Datenbank gesendet wird. Sie verarbeitet sie jedoch auf der c # -Seite 1 um 1. (Sie können dies mit dem SQL-Profiler überprüfen). Sie könnten Ihre Schleife ändern und Verwenden Sie "Überspringen" und "Nehmen", um sicherzustellen, dass Sie einen einzelnen Artikel erhalten. Dies ist jedoch nicht das, wofür ef gemacht ist. Daher bin ich mir nicht sicher, ob Sie eine bewährte Methode finden werden.
Joost K
Wenn ich das richtig verstehe, stellt SqlDataReader eine Verbindung zur Datenbank her und ruft Teile ab, während Sie Read () iterieren. Wenn der Enumerator hier genauso funktioniert, sollte es in Ordnung sein. Aber wenn es alles puffert und dann iteriert, haben wir ein Problem. Wer hier kann bestätigen, wie das funktioniert? Ich möchte, dass es eine einzelne Abfrage ausführt, aber eine Stream-Verbindung zur Datenbank hat und mit den Daten arbeitet, wobei jeweils eine Entität verarbeitet und freigegeben wird.
Atle S
Warum speichern Sie Ihren Code nicht im Speicher? Das können wir nicht für Sie tun. Außerdem ist die Frage aufgrund unbekannter Komponenten und des umgebenden Codes weit gefasst / unklar (und würde als solche zurückgestellt, wenn es nicht das Kopfgeld gäbe). (Wie, woher kommt streamdas?). Schließlich erfordert der schnelle Umgang mit SQL Server-Dateistreamdaten und das Streaming einen anderen Ansatz, der über EF hinausgeht.
Gert Arnold

Antworten:

1

Wenn Sie einen LINQ-Filter für eine Entität erstellen, ist dies im Allgemeinen wie das Schreiben einer SQL-Anweisung in Codeform. Es wird eine zurückgegeben IQueryable, die nicht tatsächlich für die Datenbank ausgeführt wurde. Wenn Sie IQueryablemit einem foreachoder einen Aufruf ToList()durchlaufen, wird die SQL ausgeführt und alle Ergebnisse werden zurückgegeben und im Speicher gespeichert.

https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/ef/language-reference/query-execution

Während EF möglicherweise nicht die beste Wahl für reine Leistung ist, gibt es eine relativ einfache Möglichkeit, dies zu handhaben, ohne sich über die Speichernutzung Gedanken zu machen:

Folgendes berücksichtigen

var filteredIds = BlobDbContext.Blobs
                      .Where(b => b.SomeProperty == "SomeValue")
                      .Select(x => x.Id)
                      .ToList();

Jetzt haben Sie die Blobs gemäß Ihren Anforderungen gefiltert und für die Datenbank ausgeführt, aber nur die ID-Werte im Speicher zurückgegeben.

Dann

foreach (var id in filteredIds)
{
    var blob = BlobDbContext.Blobs.AsNoTracking().Single(x => x.Id == id);
    // Do your work here against a single in-memory blob
}

Der große Blob sollte für die Speicherbereinigung verfügbar sein, sobald Sie damit fertig sind, und Ihnen sollte nicht der Speicher ausgehen.

Natürlich können Sie die Anzahl der Datensätze in der ID-Liste überprüfen oder der ersten Abfrage Metadaten hinzufügen, um zu entscheiden, wie sie verarbeitet werden sollen, wenn Sie die Idee verfeinern möchten.

ste-fu
quelle
1
Dies beantwortet meine Frage nicht. Ich wollte wissen, ob EF das Abrufen aus der Abfrage in einer sequenziellen Angelegenheit behandelt, wenn der Enumerator durchlaufen wird, wie es SqlDataReader mit Next tut. Es sollte möglich sein, und es ist auch der bevorzugte Weg, anstatt eins nach dem anderen abzurufen. Das, was ich hier einer Antwort am nächsten gekommen bin, ist das, was Smit Patel in einer Antwort hier sagt : github.com/aspnet/EntityFrameworkCore/issues/14640 Er sagt: "Das bedeutet, wir müssten nicht intern puffern. Daher in Ihrem In diesem Fall würde eine Abfrage ohne Nachverfolgung nicht mehr Daten abrufen / speichern als die aktuelle Ergebniszeile. "
Atle S
Wenn Sie zu 100% bestätigen können, dass EF vor dem Aufzählen alles abruft, ist dies Teil einer Antwort, wenn Sie auch eine Möglichkeit angeben, SqlDataReader zu verwenden, um dies ordnungsgemäß zu tun. Oder wenn EF dies tatsächlich richtig macht, wäre eine Bestätigung eine Antwort. Wie auch immer, dies beginnt mehr Zeit in Anspruch zu nehmen, als ich zum Debuggen von EF für eine Bestätigung benötigen würde;)
Atle S
Entschuldigung - ich habe ein wenig gegraben, bin aber nicht auf den Grund gegangen. Ich würde vorschlagen, dass wenn Sie sich Sorgen um reine Leistung machen, EF nicht der richtige Weg ist. Wenn Sie das EF-Paradigma beibehalten möchten, stellt meine Antwort sicher, dass Ihnen nicht der Speicher ausgeht. Angenommen, der Idhat einen Clustered-Index, ist der Leistungstreffer vieler sequentieller Abfragen möglicherweise nicht so schlecht, wie Sie denken.
Ste-Fu