AspNet Core Web API和EF Core的高内存使用率

时间:2019-05-11 17:44:00

标签: c# asp.net-core asp.net-core-webapi ef-core-2.1 ef-core-2.2

调用某些端点后,我发现诊断工具上的内存使用率变得过高。

我试图将问题尽可能地缩小,因此我可以消除所有其他因素,最后得到以下结果:

    [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。

我在这里想念东西吗?

1 个答案:

答案 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},OptionsLists等)也可能是正确的,尤其是一对多的表,这很容易使正在查询的行数/列数爆炸。

一种优化此方法的潜在方法是在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());
    }