内存泄漏实体框架

Posted

技术标签:

【中文标题】内存泄漏实体框架【英文标题】:Memory leak Entity Framework 【发布时间】:2012-07-12 11:32:33 【问题描述】:

在将实体框架与 SQL Server Compact Edition 一起使用时出现内存泄漏。我的情况:

我有一个大约 600MByte 大的文件。我逐行阅读,创建一个实体类并将其添加到 SQL Server CE 数据库中。内存因此增长得非常快。 Gen 0 Collections 计数器和 Gen 2 Heap Size 增长非常快(来自 Process Explorer 的信息)。如果我理解正确的 Gen 2 Heap 是用于大对象的。我认为我的实体类是一个大对象。所以 Entity Framework 保存我的对象而不释放它们。我已经尝试将它们分离并调用 GC.Collect(2) 但它没有帮助。

首先我读了这行。然后在解析行后创建一个对象。然后将其添加到数据库。这是我的数据库代码:

DBEntities dbConnection = new DBEntities();
dbConnection.My_Table.AddObject(MyObjectCreatedFromTheLine);
dbConnection.SaveChanges();
//  dbConnection.Detach(MyObjectCreatedFromTheLine);
//  dbConnection.Dispose();
MyObjectCreatedFromTheLine = null;
dbConnection = null;

我还读到创建的实体类(MyObjectCreatedFromTheLine)属于DbContext。所以我为每一行调用这段代码,每次都创建一个新的上下文。

我做错了什么?

【问题讨论】:

您应该只使用一个上下文,将所有对象添加到上下文然后调用SaveChanges 一次。还可以使用using 语法糖来强制对您的上下文实例进行Dispose 调用。 你为什么不处理你的 dbConnection? 我使用了 dbConnection.Dispose() 但它没有帮助。同样一开始我只使用了一个上下文,问题是一样的。然后我读到,问题是上下文保留了指向对象的指针。所以我尝试这种方式,每次都创建一个新的上下文。 使用我的 SqlCeBulkCopy 库快速插入到 SQL Server Compact:sqlcebulkcopy.codeplex.com 所以 EntityFramework 不适合批量插入。但是您可以关闭 AutoDetectChangesEnabled 以减少相当多的内存使用量。在调用GC.Collect 时,还要定期更新您的 DbContext。它不会解决问题,但会让问题变得可以忍受。 【参考方案1】:

我在尝试使用实体框架将 50,000 多条记录插入 SQL 数据库时遇到了这个问题。实体框架不适用于大型批量操作(大型插入或删除操作),因此我最终使用了 System.Data.SqlClient.SqlBulkCopy 库,它更高效、更快。我什至编写了以下帮助函数来自动映射,因此我不必手动构造 SQL 插入语句。 (它与类型无关!我认为)。

基本上工作流程是:IList -> DataTable -> SqlBulkCopy

public static void BulkInsert<T>(string connection, string tableName, IList<T> list)
    
        using (var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepNulls))
        
            bulkCopy.BatchSize = list.Count;
            bulkCopy.DestinationTableName = tableName;
            bulkCopy.BulkCopyTimeout = 3000;

            var table = new DataTable();
            var props = TypeDescriptor.GetProperties(typeof(T))
                //Dirty hack to make sure we only have system data types 
                //i.e. filter out the relationships/collections
                                       .Cast<PropertyDescriptor>()
                                       .Where(propertyInfo => propertyInfo.PropertyType.Namespace.Equals("System"))
                                       .ToArray();

            foreach (var propertyInfo in props)
            
                bulkCopy.ColumnMappings.Add(propertyInfo.Name, propertyInfo.Name);
                table.Columns.Add(propertyInfo.Name, Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType);
            

            var values = new object[props.Length];
            foreach (var item in list)
            
                for (var i = 0; i < values.Length; i++)
                
                    values[i] = props[i].GetValue(item);
                

                table.Rows.Add(values);
            

            bulkCopy.WriteToServer(table);
        
    

在我的示例中,我从 15-20 分钟插入到不到一分钟。

【讨论】:

【参考方案2】:

我认为你的方法不对。只需创建一个DBEntities 对象即可保存所有更改。像下面这样的东西可能会起作用;

using(DBEntities dbConnection = new DBEntities())

    foreach(MyObjectCreatedFromTheLine entity in ListOfMyObjectCreatedFromTheLine)
    
        dbConnection.My_Table.AddObject(MyObjectCreatedFromTheLine);
    
    dbConnection.SaveChanges();

您正在为每个实体创建一个新的DBEntities 对象,这根本不对。只是将 dbConnection 设置为 null 并不意味着对象已被释放或垃圾收集器不会收集它。其实你只是将引用设置为null,对象还在内存中,垃圾回收器会回收对象。

【讨论】:

我试试看。现在我的内存泄漏增长得更快。我敢肯定,直到我不只是在堆中调用 SaveChanges() 。所以工作集正在增长。 你监控内存多久了?为了被称为​​内存泄漏,消耗的内存在较短的时间内不得减少(或增加)。【参考方案3】:

我不认为通过数据上下文添加大量实体是最好的方法。对于每个创建的对象,您都会消耗内存,因为数据上下文有一个内部的一级缓存,对象会一直保留到上下文被释放。

我不太了解EF,也不知道每次持久化一个对象时是否可以清除缓存。但是,我宁愿选择根本不使用 EF 来执行大量插入。

改为使用SqlBulkCopy 类。它应该可以解决您的内存问题,而且它比您使用 EF 和每个对象插入所能实现的任何东西都要快一个数量级。

【讨论】:

【参考方案4】:

让您的 DBEntities dbConnection = new DBEntities() 退出循环!?

在每次迭代中创建新的对象上下文既不相关又荒谬。

而且分配需要更多时间,尤其是对于像这样的大对象,更不用说内存开销和释放,这可能是问题所在。

【讨论】:

以上是关于内存泄漏实体框架的主要内容,如果未能解决你的问题,请参考以下文章

为啥.NET 没有内存泄漏?

jpa hibernate中扫描太多实体时,glassfish启动很慢或内存泄漏

如何解决框架布局中的内存泄漏问题?

RedisTokenStore 源码解析 以及内存泄漏问题

iPhone 帮助:CoreLocation 框架中的奇怪内存泄漏

Swift Quick 框架内存泄漏