内存泄漏实体框架
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
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() 退出循环!?
在每次迭代中创建新的对象上下文既不相关又荒谬。
而且分配需要更多时间,尤其是对于像这样的大对象,更不用说内存开销和释放,这可能是问题所在。
【讨论】:
以上是关于内存泄漏实体框架的主要内容,如果未能解决你的问题,请参考以下文章
jpa hibernate中扫描太多实体时,glassfish启动很慢或内存泄漏