【问题标题】:How to prevent memory overflow when using an IEnumerable<T> and Linq-To-Sql?使用 IEnumerable<T> 和 Linq-To-Sql 时如何防止内存溢出?
【发布时间】:2010-11-05 06:53:16
【问题描述】:

此问题与a previous question of mine有关

这是我当前的代码

 IEnumerable<Shape> Get()
 {
     while(//get implementation
         yield return new Shape(//...
 }

 void Insert()
 {
       var actual = Get();
       using (var db = new DataClassesDataContext())
       {
           db.Shapes.InsertAllOnSubmit(actual);
           db.SubmitChanges();
       }
 }

我遇到了内存溢出,因为 IEnumerable 太大了。如何预防?

【问题讨论】:

    标签: c# .net linq-to-sql ienumerable yield


    【解决方案1】:

    尝试使用InsertOnSubmit 而不是InsertAllOnSubmit。然后像 Erich 所说的那样以适当的时间间隔提交。

    或者,如果您想分批进行,例如5、尝试Handcraftsman's或dtb's的解决方案来获取I​​Enumerable的IEnumerable。例如,使用 dtb 的 Chunk:

       var actual = Get();
       using (var db = new DataClassesDataContext())
       {
           foreach(var batch in actual.Chunk(5))
           {
             db.Shapes.InsertAllOnSubmit(batch);
             db.SubmitChanges();
           }
       }
    

    【讨论】:

      【解决方案2】:

      使用以下扩展方法将输入分解为适当大小的子集

      public static class IEnumerableExtensions
      {
          public static IEnumerable<List<T>> InSetsOf<T>(this IEnumerable<T> source, int max)
          {
              List<T> toReturn = new List<T>();
              foreach(var item in source)
              {
                  toReturn.Add(item);
                  if (toReturn.Count == max)
                  {
                      yield return toReturn;
                      toReturn = new List<T>();
                  }
              }
              if (toReturn.Any())
              {
                  yield return toReturn;
              }
          }
      }
      

      然后持久化子集

      void Insert()
      {
          var actual = Get();
          using (var db = new DataClassesDataContext())
          {
              foreach (var set in actual.InSetsOf(5))
              {
                  db.Shapes.InsertAllOnSubmit(set);
                  db.SubmitChanges();
              }
          }
      }
      

      您可能还会发现 InsertOnSubmit() 与 InsertAllOnSubmit() 上的 this MSDN article 很有用。

      【讨论】:

      • 使用 toReturn.Clear() 而不是 toReturn = new List 来避免开销。这类似于stackoverflow.com/questions/1008785/…,但更明确一些。
      • 清除列表而不是创建新列表的副作用是意外更改先前返回的结果,如果调用者尚未使用该结果,则会出现问题。例如: Enumerable.Range(1, 100).InSetsOf(5).InSetsOf(5).ToList().ForEach(x => Console.WriteLine(x.First().First() + "-" + x .Last().Last()));得到 1-25 26-50 51-75 76-100 作为编码,但如果列表只被清除,则为 21-25 46-50 71-75 96-100。此外,由于不使用 GroupBy,它可以延迟返回结果,而不是先消耗整个输入。
      【解决方案3】:

      有关从 IEnumerable 获取成批项目的简洁方法,请参阅:

      C#: Cleanest way to divide a string array into N instances N items long

      更新:不好,这适用于数组。如果以后有时间,没有其他人提供什么,我会写下来......

      【讨论】:

      • 这对他有用吗?他不知道大小,因为他只有一个 IEnumerable。
      • Eric Lippert 将 Erich 指向解决方案 (stackoverflow.com/questions/1008785#answer-1008855)。 dtb 给出了一个函数,它接受一个 IEumearable 并返回一个 IEnumerable>。每个内部 IEnumerable 最多有(例如)5 个元素。
      【解决方案4】:

      一种选择是将其分成多个批次。创建Shape 对象的临时缓冲区,迭代直到填满它或从枚举器中耗尽,然后执行InsertBatchOnSubmit。

      【讨论】:

      • 如何获得 5 个一组的所有元素?
      • 我知道 InsertBatchOnSubmit 将是具有较少元素的 InsertAllOnSubmit
      • Earwicker 的链接有一个很好的例子。不过,我不确定这会对您有所帮助,因为您正在进行延迟执行。您可能必须在循环之外有一个 List 和 batchSize = 5 。从您的枚举器中添加项目,在计数达到 batchSize 时插入,然后清除上一个批次。你问的是这个吗?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-20
      相关资源
      最近更新 更多