C# EntityFramework IQueryable 内存泄漏

Nic*_*cer 7 c# memory-leaks entity-framework-core .net-core

我们看到内存资源没有被释放:

在此输入图像描述

使用 .NET Core 的以下代码:

class Program
{
    static void Main(string[] args)
    {
        while (true) {          
            var testRunner = new TestRunner();
            testRunner.RunTest();
        }
    }
}

public class TestRunner {
    public void RunTest() {
        using (var context = new EasyMwsContext()) {
            var result = context.FeedSubmissionEntries.Where(fse => TestPredicate(fse)).ToList();
        }
    }

    public bool TestPredicate(FeedSubmissionEntry e) {
        return e.AmazonRegion == AmazonRegion.Europe && e.MerchantId == "1234";
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我删除测试谓词,.Where我会得到预期的直线,使用谓词,内存将继续无限期地增加。

因此,虽然我可以解决问题,但我想了解发生了什么?

编辑:

将该行更改为:

public void RunTest() {
    using (var context = new EasyMwsContext()) {
        var result = context.FeedSubmissionEntries.ToList();
    }
}
Run Code Online (Sandbox Code Playgroud)

给出图表: 在此输入图像描述

所以我也不认为这是由于客户端评估造成的?

编辑2:

使用 EF Core 2.1.4

和对象堆: 在此输入图像描述

编辑3:

添加了保留图,似乎是 EF Core 的问题?

在此输入图像描述

Pan*_*vos 5

我怀疑罪魁祸首不是内存泄漏,而是 EF Core客户端评估的一个相当不幸的补充。与 LINQ-to-SQL 一样,当遇到无法转换为 SQL 的 lambda/函数时,EF Core 将创建一个更简单的查询来读取更多数据并在客户端上评估该函数。

在您的情况下,EF Core 无法知道TestPredicate是什么,因此它将读取内存中的每条记录,然后尝试过滤数据。

顺便说一句,这就是2018 年 10 月 4 日上星期四 SO 迁移到 EF Core 时发生的情况。查询返回的不是几十行,而是……5200 万行:

var answers = db.Posts
                .Where(p => grp.Select(g=>g.PostId).Contains(p.Id))
                ...
                .ToList();
Run Code Online (Sandbox Code Playgroud)

客户评估是可选的,但默认情况下处于启用状态。每次执行客户端评估时,EF Core 都会记录一条警告,但如果您尚未配置 EF Core 日志记录,这将无济于事。

安全的解决方案是禁用客户端评估,如文档的可选行为:抛出客户端评估异常部分所示,无论是在每个上下文的OnConfiguring方法中还是在 Startup.cs 配置中全局:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlServer(...)
        .ConfigureWarnings(warnings => 
                           warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
}
Run Code Online (Sandbox Code Playgroud)

更新

找出泄漏的快速方法是在“诊断”窗口中拍摄两个内存快照,并检查创建了哪些新对象以及它们使用了多少内存。很有可能是客户评价出了问题。


小智 4

我最终遇到了同样的问题。一旦我知道问题是什么,我就可以在 EntityFrameworkCore 存储库中找到它的错误报告。

简而言之,当您在 an 中包含实例方法时,IQueryable它会被缓存,并且即使在您的上下文被释放后,这些方法也不会被释放。

目前看来解决该问题并没有取得太大进展。我会密切关注它,但现在我相信避免内存泄漏的最佳选择是:

  1. 重写您的方法,以便您的方法中不包含任何实例方法IQueryable
  2. 在使用包含实例方法的 LINQ 方法之前将 转换IQueryablelistwith ToList()(如果您尝试限制数据库查询的结果,则不理想)
  3. 让您调用的方法static限制内存堆积量