您的位置:首页 > 其它

关于 Linq to EF 的内存泄漏问题

2014-01-24 18:23 573 查看
查到一些解决方案:          

 

1, http://www.codethinked.com/keep-your-iqueryable-in-check

自定义常用方法,屏蔽IQuery功能。这个好像有点靠谱。但麻烦。

 

2,http://stackoverflow.com/questions/123057/how-do-i-avoid-a-memory-leak-with-linq-to-sql

这个看起来是应对 ObjectContext的。

 

3,http://stackoverflow.com/questions/19116851/entity-framework-using-repository-pattern-unit-of-work-and-unity

这个看起来简单些 ,用using.

 

4, 直接用using的方式

using(dbcontext)

{

}

 

5, http://blog.robustsoftware.co.uk/2008/11/clearing-cache-of-linq-to-sql.html

这个针对 DataContext的一个扩展方法。

 

 

最后没有更好的解决办法。

 

 

/******************************Edit 2014-1-25**************************************************************/

 

最后在分析了DbContext的源代码以后,发现了其中的奥秘。

首先DbContext本身是对于ObjectContext的一个包装,在使用IQueryable以后会将Enitiy存于 objectStateManager ,在快速的循环中,_unchangedEntityStore不会被释放,一直被缓存,所以呢?你会发现程序的内存在不断的上涨,这不应该算是内存泄漏。 由于 _unchangedEntityStore 中的值变得越来越大,程序在查询前先去 _unchangedEntityStore 中查找,由于Dictionary<EntityKey, EntityEntry> 在很大的情况下,查询性能会急剧下降,所以程序变慢。 直到慢得你无法忍受。

 

下面是基本代码。

 

[code]public class Class1


{


public static void Test()


    {


Domain.Entities.Models.passportContext context =


new Domain.Entities.Models.passportContext("passportContext");


context.Configuration.ProxyCreationEnabled = false;


context.Configuration.LazyLoadingEnabled = false;


 


var start = int.MaxValue;


 


 


var om = ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager;


 


om.ObjectStateManagerChanged += om_ObjectStateManagerChanged;


 


while (true)


   {


var list = context.UserSources.OrderByDescending(x => x.UserId).Where(x => x.UserId < start).Take(100).ToList();


 


 


 


 


ObjectContext oc = ((IObjectContextAdapter)context).ObjectContext;


 


var m = oc.ObjectStateManager;


var a = list.First();


 


var count = GetUnchangedCount(context);


 


var mth = m.GetType().GetMethod("GetObjectStateEntriesCount", BindingFlags.Instance | BindingFlags.NonPublic);


 


             var unchanged = mth.Invoke(m, new object[]{ EntityState.Unchanged});


 


if (a.UserId % 4 == 0)


  {


a.FromSite += 1;


context.Entry(a).State = System.Data.EntityState.Modified;


}


 


 


count = GetUnchangedCount(context);


 


ClearUnchangedCache(context);


 


Console.WriteLine(a.UserId);


 


//ClearCache(context);


start = a.UserId;


}


 


}


 


static void om_ObjectStateManagerChanged(object sender, System.ComponentModel.CollectionChangeEventArgs e)


    {


Console.WriteLine("[]" + e.Action + "" + e.Element.GetType() + "::" + e.Element);


}


 


 


public static void ClearCache(DbContext context)


    {


ObjectContext oc = DbContext2ObjectContext(context);


 


const BindingFlags flags = BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance;


 


var c = oc.GetType().GetField("_cache", flags);


 


//var value = c.GetValue(oc);


 


c.SetValue(oc, null);


 


//value = c.GetValue(oc);


}


 


 


static int GetUnchangedCount(DbContext context)


    {


 


var objectStateManager = DbContext2ObjectStateManager(context);


 


var mth = objectStateManager.GetType().GetMethod("GetObjectStateEntriesCount", BindingFlags.Instance | BindingFlags.NonPublic);


 


         var unchanged = mth.Invoke(objectStateManager, new object[]{ EntityState.Unchanged});


 


return (int)unchanged;


}


 


 


static ObjectContext DbContext2ObjectContext(DbContext context)


    {


return ((IObjectContextAdapter)context).ObjectContext;


}


 


static ObjectStateManager DbContext2ObjectStateManager(DbContext context)


    {


return DbContext2ObjectContext(context).ObjectStateManager;


}


 


static void ClearUnchangedCache(DbContext context)


    {


var objectStateManager = DbContext2ObjectStateManager(context);


 


const BindingFlags flags = BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance;


 


var c = objectStateManager.GetType().GetField("_unchangedEntityStore", flags);


 


c.SetValue(objectStateManager, null);


}


 


}

[/code]

 

 

然后发现内存还是疯长。





 

那就再加一个方法。

[code]static void ClearKeylessEntityCache(DbContext context)


 {


  var objectStateManager = DbContext2ObjectStateManager(context);


 


  const BindingFlags flags = BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance;


 


var c = objectStateManager.GetType().GetField("_keylessEntityStore", flags);


 


  c.SetValue(objectStateManager, null);


 }

[/code]

 

 

再看内存,





 

这样世界顿时安静了下来。

 

 

 

 

 

最后说明:

这个方法的确不是一个好的办法,但是由于EF的对象管理机制决定。 人家缓存也有人家缓存的道理。

实际上MS也没有打算让你按这样的方法来用,

用using(dbcontext)

{

…..

}

这样的方式反而是保持了 Kiss。

 

 

对于这样的“改进” 会不会有什么影响呢?

这个就再开个文章再来测试了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: