C# 实体数据查询与内存泄漏

C# 实体数据查询与内存泄漏,c#,entity-framework,memory-leaks,C#,Entity Framework,Memory Leaks,我在一个循环中下载了很多数据,但在一些操作之后我删除了它们,但我看到的是内存分配增长非常快,只有几秒钟和1GB,所以如何在每次迭代后进行清理 using (var contex = new DB) { var inputs = contex.AIMRInputs.Where(x => x.Input_Type == 1); foreach (var input in inputs) { var da

我在一个循环中下载了很多数据,但在一些操作之后我删除了它们,但我看到的是内存分配增长非常快,只有几秒钟和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();


        }
    }

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


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

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

对于
DbContext
(EF>=4.1):

以及:

编辑

对于EF 4.0,您可以保留查询,但使用块在
中添加以下内容作为前两行:

contex.AIMRInputs.MergeOption = MergeOption.NoTracking;
contex.Values.MergeOption = MergeOption.NoTracking;
这将禁用
ObjectContext
的更改跟踪

编辑2

…特别是以下@James Reategui的评论
AsNoTracking
减少了内存占用:

这通常是正确的(就像在这个问题的模型/查询中),但并不总是如此!实际上使用
AsNoTracking
在内存使用方面可能会适得其反

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

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

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

第二点在加载相关实体时变得尤为重要。简单的例子:

比如说,我们有一个
订单
和一个
客户
实体,一个订单有一个客户
订单。客户
。假设
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
,也不要惊讶。最后,您必须测量内存占用量,以便做出支持或反对的可靠决策,因为notracking

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