C# 实体框架内核中的内存管理
我试图请求大量数据,然后将其解析为报告。问题是,我请求的数据有2700万行记录,每行有6个连接,当通过实体框架加载时,这些连接使用所有服务器RAM。我实现了一个分页系统,将处理缓冲成更小的块,就像IO操作一样 我请求10000条记录,将它们写入一个文件流(到磁盘),我试图从内存中清除10000条记录,因为它们不再需要了 我在垃圾收集数据库上下文时遇到问题。我尝试过处理对象,清空引用,然后在下一批10000条记录上创建一个新上下文。这似乎不起作用。 (这是ef core上的一个开发人员推荐的:) 我看到的唯一其他选择是使用原始SQL查询来实现我想要的。我正在尝试构建一个系统来处理任何请求大小,唯一的可变因素是生成报告所需的时间。我可以通过EF上下文来摆脱加载的实体吗C# 实体框架内核中的内存管理,c#,entity-framework,asp.net-core,C#,Entity Framework,Asp.net Core,我试图请求大量数据,然后将其解析为报告。问题是,我请求的数据有2700万行记录,每行有6个连接,当通过实体框架加载时,这些连接使用所有服务器RAM。我实现了一个分页系统,将处理缓冲成更小的块,就像IO操作一样 我请求10000条记录,将它们写入一个文件流(到磁盘),我试图从内存中清除10000条记录,因为它们不再需要了 我在垃圾收集数据库上下文时遇到问题。我尝试过处理对象,清空引用,然后在下一批10000条记录上创建一个新上下文。这似乎不起作用。 (这是ef core上的一个开发人员推荐的:)
private void ProcessReport(ZipArchive zip, int page, int pageSize)
{
using (var context = new DBContext(_contextOptions))
{
var batch = GetDataFromIndex(page, pageSize, context).ToArray();
if (!batch.Any())
{
return;
}
var file = zip.CreateEntry("file_" + page + ".csv");
using (var entryStream = file.Open())
using (var streamWriter = new StreamWriter(entryStream))
{
foreach (var reading in batch)
{
try
{
streamWriter.WriteLine("write data from record here.")
}
catch (Exception e)
{
//handle error
}
}
}
batch = null;
}
ProcessReport(zip, page + 1, pageSize);
}
private IEnumerable<Reading> GetDataFromIndex(int page, int pageSize, DBContext context)
{
var batches = (from rb in context.Reading.AsNoTracking()
//Some joins
select rb)
.Skip((page - 1) * pageSize)
.Take(pageSize);
return batches
.Includes(x => x.Something)
}
private void ProcessReport(ZipArchive-zip、int-page、int-pageSize)
{
使用(var context=newdbcontext(_contextOptions))
{
var batch=GetDataFromIndex(页面、页面大小、上下文).ToArray();
如果(!batch.Any())
{
返回;
}
var file=zip.CreateEntry(“文件”+page+.csv”);
使用(var entryStream=file.Open())
使用(var streamWriter=newstreamwriter(entryStream))
{
foreach(批量读取var)
{
尝试
{
streamWriter.WriteLine(“在此处从记录写入数据”)
}
捕获(例外e)
{
//处理错误
}
}
}
批处理=空;
}
ProcessReport(zip,第+1页,页面大小);
}
私有IEnumerable GetDataFromIndex(int-page、int-pageSize、DBContext-context)
{
var batches=(来自context.Reading.AsNoTracking()中的rb)
//一些连接
选择rb)
.Skip((第1页)*页面大小)
。取(页面大小);
返回批次
.包括(x=>x.Something)
}
这是由于MARS(多个活动结果集被禁用)
除了内存管理问题之外,使用分页来解决这个问题会很不方便。在服务器上运行分页查询将变得昂贵。你不需要翻页。只需迭代查询结果(即不调用ToList()或ToArray()) 另外,在分页时,您必须向查询添加顺序,否则SQL可能返回重叠行,或者有间隙。请参阅SQL Server,例如:EF Core不强制执行此操作,因为某些提供程序可能会保证分页查询总是以相同的顺序读取行 下面是一个EF Core(1.1 on.NET Core)在不增加内存使用的情况下浏览巨大结果集的示例:
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System;
using System.ComponentModel.DataAnnotations.Schema;
namespace efCoreTest
{
[Table("SomeEntity")]
class SomeEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime CreatedOn { get; set; }
public int A { get; set; }
public int B { get; set; }
public int C { get; set; }
public int D { get; set; }
virtual public Address Address { get; set; }
public int AddressId { get; set; }
}
[Table("Address")]
class Address
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public string Line1 { get; set; }
public string Line2 { get; set; }
public string Line3 { get; set; }
}
class Db : DbContext
{
public DbSet<SomeEntity> SomeEntities { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=.;Database=efCoreTest;Integrated Security=true");
}
}
class Program
{
static void Main(string[] args)
{
using (var db = new Db())
{
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
db.Database.ExecuteSqlCommand("alter database EfCoreTest set recovery simple;");
var LoadAddressesSql = @"
with N as
(
select top (10) cast(row_number() over (order by (select null)) as int) i
from sys.objects o, sys.columns c, sys.columns c2
)
insert into Address(Id, Line1, Line2, Line3)
select i Id, 'AddressLine1' Line1,'AddressLine2' Line2,'AddressLine3' Line3
from N;
";
var LoadEntitySql = @"
with N as
(
select top (1000000) cast(row_number() over (order by (select null)) as int) i
from sys.objects o, sys.columns c, sys.columns c2
)
insert into SomeEntity (Name, Description, CreatedOn, A,B,C,D, AddressId)
select concat('EntityName',i) Name,
concat('Entity Description which is really rather long for Entity whose ID happens to be ',i) Description,
getdate() CreatedOn,
i A, i B, i C, i D, 1+i%10 AddressId
from N
";
Console.WriteLine("Generating Data ...");
db.Database.ExecuteSqlCommand(LoadAddressesSql);
Console.WriteLine("Loaded Addresses");
for (int i = 0; i < 10; i++)
{
var rows = db.Database.ExecuteSqlCommand(LoadEntitySql);
Console.WriteLine($"Loaded Entity Batch {rows} rows");
}
Console.WriteLine("Finished Generating Data");
var results = db.SomeEntities.AsNoTracking().Include(e => e.Address).AsEnumerable();
int batchSize = 10 * 1000;
int ix = 0;
foreach (var r in results)
{
ix++;
if (ix % batchSize == 0)
{
Console.WriteLine($"Read Entity {ix} with name {r.Name}. Current Memory: {GC.GetTotalMemory(false) / 1024}kb GC's Gen0:{GC.CollectionCount(0)} Gen1:{GC.CollectionCount(1)} Gen2:{GC.CollectionCount(2)}");
}
}
Console.WriteLine($"Done. Current Memory: {GC.GetTotalMemory(false)/1024}kb");
Console.ReadKey();
}
}
}
}
注意,EF Core中内存消耗过多的另一个常见原因是查询的“”。有关更多信息以及如何禁用自动客户端查询评估,请参阅文档 你说的“数据”是什么意思?如果对某种类型的DTO对象使用投影查询,或者不使用跟踪查询,则
DbContext
不会在内部存储任何内容。也不要使用ToList
、ToArray
等。只需枚举结果。我已使用.AsNoTracking()
关闭了查询的更改跟踪。我还删除了ToArray()
,但是垃圾收集器仍然没有释放任何上下文。至于我所说的数据,我字面上的意思是,我希望从数据库中获取记录到一个C#模型中,以便临时使用,然后进行处理。我有一种感觉,对象不会因为递归循环而从内存中删除?不要使用EF Core,这不是合适的情况。使用原始查询,您可以始终读取数据的子集,即使用数据读取器。我尝试了这种方法,但即使没有更改跟踪,ef也会执行查询并将所有数据拉回到内存中<代码>读取名称为575430的实体250000。当前内存:3678275kb GC的Gen0:16 Gen1:9 Gen2:6似乎只有在添加include时内存使用才会增加。你知道我为什么以及如何克服这个问题吗?你能制作一个复制品,或者修改我发布的一个来显示内存使用的增加吗?作为一种解决方法,您可以在查询中始终展平对象图。我有一个author表,它与book表有一对多的关系。我增加了550000名作者和2750000本书,并给每个作者5本书(没有重叠作者的书)。然后,我选择了所有作者并加入了book表。我试图创建一个repo,但是我没有好的方法让你得到一个不是先编码的大小的数据库。repo主要是为你准备的。在列举作者时,您是否看到内存利用率的增加?
Generating Data ...
Loaded Addresses
Loaded Entity Batch 1000000 rows
Loaded Entity Batch 1000000 rows
. . .
Loaded Entity Batch 1000000 rows
Finished Generating Data
Read Entity 10000 with name EntityName10000. Current Memory: 2854kb GC's Gen0:7 Gen1:1 Gen2:0
Read Entity 20000 with name EntityName20000. Current Memory: 4158kb GC's Gen0:14 Gen1:1 Gen2:0
Read Entity 30000 with name EntityName30000. Current Memory: 2446kb GC's Gen0:22 Gen1:1 Gen2:0
. . .
Read Entity 9990000 with name EntityName990000. Current Memory: 2595kb GC's Gen0:7429 Gen1:9 Gen2:1
Read Entity 10000000 with name EntityName1000000. Current Memory: 3908kb GC's Gen0:7436 Gen1:9 Gen2:1
Done. Current Memory: 3916kb