调用某些端点后,我发现诊断工具上的内存使用率变得过高。
我试图将问题尽可能地缩小,因此我可以消除所有其他因素,最后得到以下结果:
[HttpGet("test")]
public ActionResult Test()
{
var results = _context.Products
.Include(x => x.Images)
.Include(x => x.Options)
.ThenInclude(x => x.Lists)
.ThenInclude(x => x.PriceChangeRule)
.Include(x => x.Options)
.ThenInclude(x => x.Lists)
.ThenInclude(x => x.Items)
.ThenInclude(x => x.PriceChangeRule)
.Include(x => x.Options)
.ThenInclude(x => x.Lists)
.ThenInclude(x => x.Items)
.ThenInclude(x => x.SupplierFinishingItem)
.ThenInclude(x => x.Parent)
.Include(x => x.Category)
.ThenInclude(x => x.PriceFormation)
.ThenInclude(x => x.Rules)
.Include(x => x.Supplier)
.ThenInclude(x => x.PriceFormation)
.ThenInclude(x => x.Rules)
.Include(x => x.PriceFormation)
.ThenInclude(x => x.Rules)
.AsNoTracking().ToList();
return Ok(_mapper.Map<List<AbstractProductListItemDto>>(results));
}
这是一个很大的查询,包含很多包含,但是从数据库返回的数据量不是很大,大约10.000个项目。当我序列化此结果时,它只有3.5Mb。
我的API使用了大约300Mb的内存,那么当我调用此测试端点时,该值将达到约1.2Gb。我认为对于3.5Mb的数据来说这太过分了,但是我不知道EF Core在内部如何工作,因此我将忽略它。
我的问题是,据我了解,DbContext
是作为作用域服务添加的,因此它是在请求启动时创建的,然后在请求完成时终止的。这是我的注册方式:
services.AddDbContext<DatabaseContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
如果我的理解是正确的,那么在请求完成后,应该处理大量内存,对吗?
问题是我的内存使用量再也回不来了,我尝试手动处理上下文,也手动调用垃圾回收器,但是内存保持在1.2Gb。
我在这里想念东西吗?
答案 0 :(得分:2)
我看到的一个潜在领域是,您从数据库中加载的数据可能比序列化到AbstractProductListItemDto
所需的数据多得多。例如,您的Product
可能具有如下字段:
public class Product
{
public string Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
但是,您最终的 DTO 可能仅具有其中一个或两个属性,例如:
public class AbstractProductListItemDto
{
public string ProductId { get; set; }
public string ProductName { get; set; }
}
对于您要包含的其他表({{1},Options
,Lists
等)也可能是正确的,尤其是一对多的表,这很容易使正在查询的行数/列数爆炸。
一种优化此方法的潜在方法是在LINQ查询中自己进行投影。这将利用EF Core的功能,该功能仅从您指定的数据库中选择列。例如:
这将从产品表中选择所有列
Rules
这将从产品表中仅选择Id和Name列,从而减少内存使用量
var results = _context.Products.ToList();
从这个问题我不知道您要映射的所有项目的所有属性,因此,如果您想手动进行映射,则取决于您。关键部分是您需要在对查询的var results = _context.Products.Select(x => new ProductDto {
Id = x.Id,
Name = x.Name,
}
调用之前,先对Select()
进行调用。
但是,如果您使用的是Automapper,则可能会有一个捷径
Automapper包含一个尝试为您编写这些查询投影的快捷方式。它可能不起作用,具体取决于Automapper中发生了多少附加逻辑,但是值得一试。 You would want to read up on the ProjectTo<>()
method。如果您使用投影,则代码可能看起来像这样:
编辑:在注释中正确指出,使用ToList()
时不需要Include()调用。这是一个较短的示例,下面包含原始示例
已更新:
ProjectTo<>()
原始
:using AutoMapper.QueryableExtensions;
// ^^^ Added to your usings
//
[HttpGet("test")]
public ActionResult Test()
{
var projection = _context.Products.ProjectTo<AbstractProductListItemDto>(_mapper.ConfigurationProvider);
return Ok(projection.ToList());
}