实体框架核心在没有内存溢出的情况下遍历大blob数据,这是最佳实践



我正在编写遍历大量图片数据的代码,准备一个大的delta块,其中包含所有压缩后的数据以供发送。

下面是一个关于这些数据如何成为的示例

[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记录填满内存,或者在我迭代枚举器时会逐一处理它们。它不使用任何ToList,只使用枚举器,所以实体框架应该能够在移动中处理它,但我不确定它是否就是这样做的。

这里的任何实体框架专家都可以就如何正确处理这一问题提供一些指导。

通常,当您在实体上创建LINQ筛选器时,就像以代码形式编写SQL语句一样。它返回一个实际上尚未对数据库执行的IQueryable。当您使用foreach或调用ToList()迭代IQueryable时,将执行sql,并返回所有结果,并将其存储在内存中。

https://learn.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列表中的记录数量,或者如果您想改进想法,您可以将元数据添加到第一个查询中,以帮助您决定如何处理它。

只要你像notracking所示的那样直接使用它,它只会在你迭代时读取你获取和处理的部分,所以我的第一个假设是正确的。

最新更新