【问题标题】:Split results from DB into "chunks" of 10将数据库中的结果拆分为 10 个“块”
【发布时间】:2012-07-18 13:20:37
【问题描述】:

下午,

我需要一只手将 1200 多个结果分成 10 个“块”,以便我可以使用亚马逊 MWS API 处理这些结果。任何人都可以就我将如何做这件事提供任何指导吗?

 List<string> prodASIN = dc.aboProducts.Select(a => a.asin).Take(10).ToList();

我目前有这个,它有效。但是我有 1200 多个结果,需要遍历每 10 个结果,以便我可以处理它们并将它们传递给亚马逊 MWS API

【问题讨论】:

    标签: asp.net linq amazon-mws


    【解决方案1】:

    我知道这个问题已经得到解答,但我不能拒绝你this little extension method 我曾经提出过,从那以后对我很有帮助。

    你可以这样做:

    foreach(var list in prodASINs.ToChunks(10))
    {
        // send list
    }
    

    【讨论】:

      【解决方案2】:

      为什么不试试这样的:

      //Load all the database entries into Memory
      List<string> prodASINs = dc.aboProducts.Select(a => a.asin).ToList();
      var count = prodASINs.Count();
      //Loop through passing 10 at a time to AWS
      for (var i = 0; i < count; i++)
      {
          var prodASINToSend = prodASINs.Skip(i * 10).Take(10).ToList(); 
          //Send to AWS
      }
      

      或者,如果您不想将它们全部加载到内存中。

      var count = dc.aboProducts.Count();
      for (var i = 0; i < count; i++)
      {
          List<string> prodASIN = dc.aboProducts.OrderBy(a => a.Id).Select(a => a.asin).Skip(i * 10).Take(10).ToList(); 
          //Send to AWS
      }
      

      【讨论】:

      • @thatuxguy 如果使用第二个选项,您可能需要订购结果。
      • 第一种方法更好 :) 现在传递给亚马逊,看看是否一切正常 facepalm 我有这个 API!
      • 第一个解决方案可以轻松地使用 GetRange() 避免 Skip/Take/ToList 调用,第二种方法对数据库运行 1001 次查询以检索 1000 条记录(每条记录 1 条查询)。但我同意代码“更好”,看起来很漂亮,并且使用了像 Skip/Take 这样的花哨的方法调用。等一下。我得走了,我看到了一些闪亮的东西......
      【解决方案3】:

      抱歉,这不是 LINQ 特定的,但也许它会有所帮助...

      在使用 MWS 和 ERP 软件处理数据时,我所做的其中一件事是向数据库中添加一个控制列,例如“addASIN”。在数据库中,我将控制列定义为布尔值(或 TINYINT( 1) 在 MySQL 中) 并将所有新条目的标志默认为 0,并在添加条目时将其设置为 1。

      如果你能做到,那么你可以做类似的事情

      SELECT asin FROM datasource WHERE addedASIN = 0 LIMIT 10;
      

      然后,一旦 MWS 返回成功添加,则使用

      更新标志
      UPDATE datasource SET addedASIN = 1 WHERE asin = 'asinnumber';
      

      我发现这样做的好处是您的数据库将能够以最少的数据重复来停止和启动 - 例如在我的情况下(以及启动此控制列的原因)我们的网络连接可能不稳定,所以我在订单导入期间发现我会失去连接,导致订单丢失,或者订单被上传到我们的系统两次。

      此解决方案通过最多两次添加由于连接丢失而导致的 1 个订单,并且为了将该订单上传两次,需要在将数据发送到我们的 ERP 系统之间丢失连接,我们的 ERP 系统确认已添加并更新数据库,往返大约需要 30 秒。

      【讨论】:

      • 感谢您的反馈,我的大部分数据库都有“位”字段,因此它知道何时更新某些内容。真希望 API 使用起来更简单一点,哈哈
      • 哈,我知道你的意思。使用位域来控制您获取的数据,这样您可以一次将结果限制为 10 个,而不是一次性返回整个数据集呢?这可能需要一些额外的处理,但我发现有时使用 MWS 最快的方式是最长的路线..
      • 我打算使用 datetime 字段,所以如果他们 datetime = today 忽略 ;) 这样低价只能每天更新 :)
      【解决方案4】:

      切片扩展(用于数组):

          public static T[] Slice<T>(this T[] source, int index, int length)
          {
              T[] slice = new T[length];
              Array.Copy(source, index, slice, 0, length);
              return slice;
          }
      

      Array.Copy 非常快,比 Select/Skip/Take 模式快很多。虽然这种方法不是我发现的禁食方法,但最近的测试表明它比用于拆分列表和数组的 Skip/Take 模式快了近 400 倍。

      按原样使用:

      const int arraySize = 10;
      List<string> listSource = whatever;
      string[] source = listSource.ToArray();
      
      for (int i = 0; i < source.Length; i += arraySize)
      {
          List<string> buffer = source.Slice(i, arraySize).ToList();
          DoSomething(buffer);
      }
      

      【讨论】:

      • 如何让数组返回到request.ASINList.ASIN = prodASIN; 这需要一个列表
      • @thatuxguy - 我终于开始测试这个解决方案并更新了我的答案。
      【解决方案5】:

      List&lt;T&gt; 有一个名为GetRange() 的内置函数,它是专门为你想要做的事情而设计的。它非常快,不需要 Linq、casting 等......

      List<string> prodASINs = dc.aboProducts.Select(a => a.asin).ToList(); 
      
      for(int i = 0; i < prodASINs.Count; i += 10)
      {
      
          List<string> buffer = prodASINs.GetRange(i, 10);
          // do something with buffer
      }
      

      就是这样。很简单。


      测试结果:GetRange vs. Slice vs. Linq List&lt;string&gt; 有 5000 个字符串 如您所见,使用 Linq 的 Skip/Take 方法比 Slice&lt;T&gt;() 慢 383 倍以上,比 GetRange() 慢 4,736 倍

      ================================================ ====================================

      GetRange took on average 168 ticks
      Slice took on average 2073 ticks
      Linq took on average 795643 ticks
      

      使用的测试方法(自己试试):

      private static void GetRangeVsSliceVsLinq()
      {
          List<string> stringList = new List<string>();
          for (int i = 0; i < 5000; i++)
          {
              stringList.Add("This is a test string " + i.ToString());
          }
      
          Stopwatch sw = new Stopwatch();
      
          long m1 = 0, m2 = 0, m3 = 0;
      
      
          for (int x = 0; x < 10; x++)
          {
              Console.WriteLine("Iteration {0}", x + 1);
              Console.WriteLine();
      
              sw.Reset();
              sw.Start();
      
              for (int i = 0; i < stringList.Count; i += 10)
              {
                  List<string> buffer = stringList.GetRange(i, 10);
              }
              sw.Stop();
              Console.WriteLine("GetRange took {0} msecs", sw.ElapsedMilliseconds);
              Console.WriteLine("GetRange took {0} ticks", sw.ElapsedTicks);
              m1 += sw.ElapsedTicks;
      
              sw.Reset();
              sw.Start();
      
              string[] sliceArray = stringList.ToArray();
              for (int i = 0; i < sliceArray.Length; i += 10)
              {
                  List<string> buffer = sliceArray.Slice(i, 10).ToList();
              }
              sw.Stop();
              Console.WriteLine("Slice took {0} msecs", sw.ElapsedMilliseconds);
              Console.WriteLine("Slice took {0} ticks", sw.ElapsedTicks);
              m2 += sw.ElapsedTicks;
      
              sw.Reset();
              sw.Start();
      
              var count = stringList.Count();
              for (var i = 0; i < count; i++)
              {
                  var buffer = stringList.Skip(i * 10).Take(10).ToList();
              }
      
              sw.Stop();
              Console.WriteLine("Skip/Take took {0} msecs", sw.ElapsedMilliseconds);
              Console.WriteLine("Skip/Take took {0} ticks", sw.ElapsedTicks);
              m3 += sw.ElapsedTicks;
      
              Console.WriteLine();
          }
      
          Console.WriteLine();
          Console.WriteLine("GetRange took on average {0} ticks", m1 / 10);
          Console.WriteLine("Slice took on average {0} ticks", m2 / 10);
          Console.WriteLine("Linq took on average {0} ticks", m3 / 10);
      
      }
      

      【讨论】:

      • “它非常快,不需要 Linq、casting 等……”——这很好……如果你正在使用列表。但是,考虑到此 linq 可能正在使用具有快速“跳过”的 DB 驱动程序,并且访问数据的行为可能是瓶颈 - 在这种情况下,您的示例可能表现不佳。我注意到这一点是因为在您决定发帖的同时,我无缘无故地被否决了。
      • “太棒了......如果你正在使用列表” - List&lt;string&gt; prodASIN 是一个列表,这就是这个答案所要解决的问题。如果瓶颈在数据库层,任何检索数据的尝试都将出现相同的性能问题,解决它的唯一方法是异步检索数据,例如使用 PLINQ。使用并行,可以同时检索数据块并在返回每个块时立即处理(前提是可以无序处理块)或等到所有块都返回,这仍然会更快。
      • "List prodASIN 是一个列表,"prodASIN 是查询的RESULT。例如:让我们假设一个元素的大小为 100MB [我们所知道的是它包含一个字符串] 并存储在 db 或类似文件中。您的版本将首先尝试加载所有 1200 个结果 [120GB],然后拆分它们。其他人,比如我的,会请求 1GB 块中的数据。这并不是说您的解决方案不好,而是它有其局限性——那些没有出现在被否决的答案中。
      • prodASIN 是 List&lt;string&gt;,这使它成为一个列表。 OP 可能会返回其他内容,并且由于 GetRange 被限制为 List&lt;T&gt;,因此我的解决方案将不起作用。考虑到 OP 正在加载所有结果,我假设每条记录不是 100GB,否则问题会有所不同,需要不同的内存管理方法。据我所知,该要求一次只允许上传 10 条记录。您的答案使用一种被证明比其他已知解决方案慢的方法解决了一个不是问题的问题(即GroupBy
      • "prodASIN 是 List 这使它成为一个 List。" - prodASIN 是结果。问题清楚地表明被查询的数据源是“dc.aboProducts”。虽然 GroupBy 是一个有趣的解决方案——我很确定它对某些 Linq 驱动程序不起作用——例如MongoDB。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-27
      • 1970-01-01
      相关资源
      最近更新 更多