使用C#/ ADO.Net实体复制大型数据库时内存不足

时间:2012-11-08 15:47:29

标签: c# ado.net garbage-collection

又一个如何释放内存的问题:

我正在两个数据库之间复制数据,这两个数据库目前相同但很快就会失去同步。我使用Reflection和ADO.Net实体在C#中组建了一个骨架应用程序:

对于源数据库中的每个表:

  • 清除目标数据库中的相应表格
  • 对于源表中的每个对象
    • 对于源对象中的每个属性
      • 如果目标对象中存在具有相同名称的属性,请使用Reflection将源属性复制到目标属性

这很好用,直到我到达用户上传文件的900MB大表。

将blob(每个最多7 MB)复制到我的计算机并返回到目标数据库的过程会耗尽本地内存。但是,该内存没有被释放,并且一旦复制了大约750 MB的数据,该进程就会消失 - 当抛出OutOfMemoryException时,我的程序具有1500 MB的已分配空间,大概是它到目前为止复制的所有内容的两个副本。

我先尝试了一种天真的方法,做了一个简单的复制。它一直在每张桌子上工作,直到我到达那个大桌子。我尝试强制GC.Collect(),结果没有明显变化。我也尝试将实际副本放入一个单独的函数中,希望超出范围的引用可以帮助它获得GCed。我甚至放了一个Thread.Sleep来尝试让后台进程有更多的时间来运行。所有这些都没有效果。

这是现在存在的相关代码:

public static void CopyFrom<TSource, TDest>(this ObjectSet<TDest> Dest, ObjectSet<TSource> Source, bool SaveChanges, ObjectContext context)
    where TSource : class
    where TDest : class {

    int total = Source.Count();
    int count = 0;
    foreach (var src in Source) {
        count++;
        CopyObject(src, Dest);

        if (SaveChanges && context != null) {
            context.SaveChanges();
            GC.Collect();
            if (count % 100 == 0) {
                Thread.Sleep(2000);
            }
        }
    }
}

我没有包含CopyObject()函数,它只是使用反射来评估src的属性,并将它们放在一个新对象中的同名属性中,以附加到Dest。

SaveChanges是一个传递的布尔变量,表示应该完成这个额外的处理,它只在大表上才为真,否则为false。

所以,我的问题是:如何修改此代码以防止我内存不足?

1 个答案:

答案 0 :(得分:4)

问题是您的数据库上下文在内部使用了大量缓存,并且它会保留大量信息并阻止垃圾收集器释放它(无论您是否调用Collect)。

这意味着您的上下文定义为范围太高。 (看来,根据你的编辑,你在表格中使用它。这是......不好。)你没有显示它的定义,但无论它在哪里,它应该在较低的水平。请记住,由于连接池创建新的上下文并不昂贵,并且根据您的用例,您不需要依赖大量缓存的信息(因为您不会多次触摸项目),所以经常创建新环境不应该增加性能成本,即使它大大减少了你的内存占用。