实体数据查询和内存泄漏

本文关键字:泄漏 内存 数据查询 实体 | 更新日期: 2023-09-27 18:28:45

我在循环中下载了很多数据,但经过一些操作后,我删除了它们,但我看到内存分配增长非常快,只有几秒钟和1GB,所以如何在每次迭代后进行清理?

    using (var contex = new DB)
    {
        var inputs = contex.AIMRInputs.Where(x => x.Input_Type == 1);
        foreach (var input in inputs)
        {
            var data = contex.Values.Where(x => x.InputID == input.InputID).OrderBy(x => x.TimeStamp).ToList();
            if (data.Count == 0) continue;
            foreach (var value in data)
            {
               Console.WriteLine(Value.property);
            }
            data.Clear();

        }
    }

实体数据查询和内存泄漏

您可以做的第一件事是禁用更改跟踪,因为您没有更改代码中的任何数据。这可以防止加载的对象附加到上下文:

对于DbContext(EF>=4.1):

var inputs = contex.AIMRInputs.AsNoTracking()
    .Where(x => x.Input_Type == 1);

和:

var data = contex.Values.AsNoTracking()
    .Where(x => x.InputID == input.InputID)
    .OrderBy(x => x.TimeStamp)
    .ToList();

编辑

对于EF 4.0,您可以保持查询原样,但在using块中添加以下内容作为前两行:

contex.AIMRInputs.MergeOption = MergeOption.NoTracking;
contex.Values.MergeOption = MergeOption.NoTracking;

这将禁用ObjectContext的更改跟踪。

编辑2

特别是参考@James Reategui在下面的评论,AsNoTracking减少了内存占用:

这通常是真的(就像这个问题的模型/查询中一样),但并不总是这样!实际上,使用AsNoTracking可能会对内存使用产生反作用。

当对象在内存中具体化时,AsNoTracking会做什么?

  • 第一:它不将实体附加到上下文,因此不会在上下文的状态管理器中创建条目。这些条目消耗内存。当使用POCO时,条目包含实体首次加载/附加到上下文时的属性值的快照-基本上是除了对象本身之外的所有(标量)属性的副本。因此,当不应用AsNoTracking时,所消耗的内存大约是对象大小的两倍。

  • 第二:另一方面,当实体没有连接到上下文时,EF无法利用键值和对象引用标识之间的标识映射的优势。这意味着具有相同键的对象将被物化多次,这会消耗额外的内存,而在不使用AsNoTracking的情况下,EF将确保实体每个键值只被物化一次。

当加载相关实体时,第二点变得尤为重要。简单示例:

比方说,我们有一个Order和一个Customer实体,一个订单有一个客户Order.Customer。假设Order对象大小为10字节,Customer对象大小为20字节。现在我们运行这个查询:

var orderList = context.Orders
    .Include(o => o.Customer).Take(3).ToList();

假设所有3个加载的订单都分配了相同的客户。因为我们没有禁用跟踪EF将实现:

  • 3个订单对象=3x10=30字节
  • 1个客户对象=1x20=20字节(因为上下文识别出所有3个订单的客户都是相同的,所以它只具体化了一个客户对象)
  • 具有原始值的3个订单快照条目=3x10=30字节
  • 1个具有原始值的客户快照条目=1x20=20字节

总和:100字节

(为了简单起见,我假设具有复制属性值的上下文条目与实体本身的大小相同。)

现在,我们在禁用更改跟踪的情况下运行查询:

var orderList = context.Orders.AsNoTracking()
    .Include(o => o.Customer).Take(3).ToList();

物化数据为:

  • 3个订单对象=3x10=30字节
  • 3(!)个客户对象=3x20=60字节(无标识映射=每个键有多个对象,所有三个客户对象都具有相同的属性值,但它们在内存中仍然是三个对象)
  • 没有快照条目

总和:90字节

因此,在这种情况下,使用AsNoTracking查询消耗的内存减少了10字节

现在,同样的计算有5个订单(Take(5)),同样所有订单都有相同的客户:

AsNoTracking:

  • 5个订单对象=5x10=50字节
  • 1个客户对象=1x20=20字节
  • 具有原始值的5个订单快照条目=5x10=50字节
  • 1个具有原始值的客户快照条目=1x20=20字节

总和:140字节

AsNoTracking:

  • 5个订单对象=5x10=50字节
  • 5(!)个客户对象=5x20=100字节
  • 没有快照条目

总和:150字节

这次使用AsNoTracking的成本要高出10个字节

上面的数字非常粗略,但有些地方是盈亏平衡点,使用AsNoTracking可能需要更多内存。

使用或不使用AsNoTracking之间的内存消耗差异很大程度上取决于查询、模型中的关系以及查询加载的具体数据。例如:当上述示例中的订单全部(或大部分)都有不同的客户时,AsNoTracking在内存消耗方面总是更好。

结论:AsNoTracking主要是用来提高查询性能的工具,而不是内存使用率。在许多情况下,它也会消耗更少的内存。但是,如果一个特定的查询使用AsNoTracking需要更多的内存,也不要感到惊讶。最后,您必须测量内存占用,才能做出支持或反对AsNoTracking的可靠决策。

如果这里的问题可能与DataContext有关,请分开。它们中的许多在执行查询时缓存信息或存储附加信息,因此其内存占用将随着时间的推移而增加。我会先与探查器核实一下,但如果这是你的问题,你可能需要在每次X请求后重新创建一个新的数据上下文(用不同的X值进行实验,看看什么最有效)。

我还想指出,大多数人往往记忆力很强。在开始进行这些类型的优化之前,您应该真正确定您使用的内存超过了真正可接受的内存。GC还将开始更积极地清除内存,因为您可以使用的可用内存更少。它不会过早地优化(您也不应该)。