Entity Framework Coreは、メモリオーバーフローなしで大きなblobデータをトラバースします。ベストプラクティス


8

大量の画像データをトラバースするコードを書いて、送信用に圧縮されたすべてを含む大きなデルタブロックを準備しています。

このデータの例を以下に示します

[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);
    }
}

これで作業するときは、すべてをファイルストリームに処理します。いつでもメモリにできるだけ少なくしたいです。

このようにするだけで十分ですか?

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

これでもメモリがすべてのblobレコードで満たされますか、それとも、列挙子で反復するときに1つずつ処理されますか?ToListを使用せず、列挙子のみを使用しているため、Entity Frameworkは外出先でそれを処理できるはずですが、それが何をするのかはわかりません。

これが適切に処理される方法に関するガイダンスを提供できるEntity Frameworkのエキスパート。


私は100%確実ではありませんが、これにより単一のクエリがデータベースに送信されると思いますが、c#側で1つずつ処理します(SQLプロファイラーでこれを確認できます)ループを変更して、スキップアンドテイクを使用して単一のアイテムを取得していることを確認してください。ただし、これはefの目的ではないため、ベストプラクティスを見つけるかどうかはわかりません。
Joost K

私が正しく理解していれば、Read()を繰り返している間にSqlDataReaderがデータベースに接続し、パーツをフェッチします。列挙子がここで同じように機能する場合は、問題ありません。しかし、それがすべてをバッファリングしてから繰り返すと、問題が発生します。これがどのように機能するかを確認できる人はいますか?1つのクエリを実行したいのですが、データベースへのストリーム接続があり、データを処理しながら、一度に1つのエンティティを処理して解放します。
Atle S

なぜコードをメモリプロファイリングしないのですか?それを行うことはできません。また、不明なコンポーネントと周囲のコードのために、質問は広範囲/不明確です(そして、賞金がなかった場合はそのまま保留されます)。(同様に、どこstreamから来たのですか?)。最後に、SQL Serverファイルストリームデータを高速に処理し、ストリーミングするには、EFを超える別のアプローチが必要です。
Gert Arnold

回答:


1

一般に、エンティティでLINQフィルターを作成する場合は、SQLステートメントをコード形式で記述するようなものです。IQueryableデータベースに対して実際に実行されていないを返します。または呼び出しIQueryableで繰り返し処理すると、sqlが実行され、すべての結果が返されてメモリに格納されます。foreachToList()

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

EFは純粋なパフォーマンスに最適ではないかもしれませんが、メモリ使用量をあまり気にすることなくこれを処理する比較的簡単な方法があります。

以下を検討してください

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

これで、要件に従ってBlobをフィルタリングし、これをデータベースに対して実行しましたが、メモリ内のId値のみを返しました。

その後

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
}

大きなblobは、処理が完了するとガベージコレクションに使用できるようになり、メモリ不足になることはありません。

当然、idリストのレコード数をセンスチェックすることも、最初のクエリにメタデータを追加して、アイデアを絞り込む場合の処理​​方法の決定に役立てることもできます。


1
これは私の質問の答えにはなりません。列挙子をトラバースするときに、EFがクエリからのフェッチを逐次的に処理するかどうか、SqlDataReaderがNextで行う方法を知りたかったのです。それは可能であるべきであり、1つずつフェッチするのではなく、推奨される方法でもあります。私はここで答えになってきた最も近いスミット・パテルがここに解答にこう言われる、github.com/aspnet/EntityFrameworkCore/issues/14640 彼は手段であることを、我々は内部でバッファリング必要はありませんもの」と言うそのため、あなたに。場合、非追跡クエリは、現在の結果行よりも多くのデータを取得/保存しません。
Atle S

EFが列挙する前にすべてをフェッチすることを100%確認できる場合、SqlDataReaderを使用して適切な方法で実行する方法も提供していれば、それは回答の一部になります。または、EFが実際にこれを適切に実行する場合、それを確認することが答えになります。とにかく、これは、確認のためにEFをデバッグするよりも時間がかかり始めています;)
Atle S

申し訳ありません-私は少し掘りましたが、その底に到達しませんでした。純粋なパフォーマンスを心配している場合は、EFを使用する方法ではないことをお勧めします。EFパラダイムを維持したい場合は、メモリ不足が発生しないようにしてください。にIdクラスター化インデックスがあると仮定すると、多くの順次クエリのパフォーマンスへの影響は、思ったほど悪くはないかもしれません。
ste-fu
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.