使用Linq将DataTable分解为固定大小的块的简洁方法是什么?

更新:这是一个类似的问题


假设我有一个DataTable ,里面有几千个DataRows

我想将表拆分成较小行的块进行处理。

我认为C#3改进的数据处理能力可能有所帮助。

这是我到目前为止的骨架:

 DataTable Table = GetTonsOfData(); // Chunks should be any IEnumerable type var Chunks = ChunkifyTableIntoSmallerChunksSomehow; // ** help here! ** foreach(var Chunk in Chunks) { // Chunk should be any IEnumerable type ProcessChunk(Chunk); } 

关于什么应该取代ChunkifyTableIntoSmallerChunksSomehow任何建议?

我真的很感兴趣的是如何通过访问C#3工具来实现这一目标。 如果试图应用这些工具是不合适的,请解释!


更新3(修改后的块,因为我真的想要表,而不是ienumerables;使用扩展方法 – 感谢雅各布):

最终实施:

处理分块的扩展方法:

 public static class HarenExtensions { public static IEnumerable Chunkify(this DataTable table, int chunkSize) { for (int i = 0; i < table.Rows.Count; i += chunkSize) { DataTable Chunk = table.Clone(); foreach (DataRow Row in table.Select().Skip(i).Take(chunkSize)) { Chunk.ImportRow(Row); } yield return Chunk; } } } 

该扩展方法的示例消费者,具有来自临时测试的样本输出:

 class Program { static void Main(string[] args) { DataTable Table = GetTonsOfData(); foreach (DataTable Chunk in Table.Chunkify(100)) { Console.WriteLine("{0} - {1}", Chunk.Rows[0][0], Chunk.Rows[Chunk.Rows.Count - 1][0]); } Console.ReadLine(); } static DataTable GetTonsOfData() { DataTable Table = new DataTable(); Table.Columns.Add(new DataColumn()); for (int i = 0; i < 1000; i++) { DataRow Row = Table.NewRow(); Row[0] = i; Table.Rows.Add(Row); } return Table; } } 

对于Linq的Skip and Take方法来说,这似乎是一个理想的用例,具体取决于你想要通过分块实现的目标。 这是完全未经测试的,从未在IDE代码中输入,但您的方法可能看起来像这样。

 private List> ChunkifyTable(DataTable table, int chunkSize) { List> chunks = new List>(); for (int i = 0; i < table.Rows.Count / chunkSize; i++) { chunks.Add(table.Rows.Skip(i * chunkSize).Take(chunkSize).ToList()); } return chunks; } 

这是非常易读的,只能迭代序列一次,也许可以省去重复冗余Skip() / Take()调用的相当糟糕的性能特征:

 public IEnumerable> Chunkify(DataTable table, int size) { List chunk = new List(size); foreach (var row in table.Rows) { chunk.Add(row); if (chunk.Count == size) { yield return chunk; chunk = new List(size); } } if(chunk.Any()) yield return chunk; } 

这是一种可行的方法:

 public static class Extensions { public static IEnumerable> InPages(this IEnumerable enumOfT, int pageSize) { if (null == enumOfT) throw new ArgumentNullException("enumOfT"); if (pageSize < 1) throw new ArgumentOutOfRangeException("pageSize"); var enumerator = enumOfT.GetEnumerator(); while (enumerator.MoveNext()) { yield return InPagesInternal(enumerator, pageSize); } } private static IEnumerable InPagesInternal(IEnumerator enumeratorOfT, int pageSize) { var count = 0; while (true) { yield return enumeratorOfT.Current; if (++count >= pageSize) yield break; if (false == enumeratorOfT.MoveNext()) yield break; } } public static string Join(this IEnumerable enumOfT, object separator) { var sb = new StringBuilder(); if (enumOfT.Any()) { sb.Append(enumOfT.First()); foreach (var item in enumOfT.Skip(1)) { sb.Append(separator).Append(item); } } return sb.ToString(); } } [TestFixture] public class Tests { [Test] public void Test() { // Arrange var ints = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; var expected = new[] { new[] { 1, 2, 3 }, new[] { 4, 5, 6 }, new[] { 7, 8, 9 }, new[] { 10 }, }; // Act var pages = ints.InPages(3); // Assert var expectedString = (from x in expected select x.Join(",")).Join(" ; "); var pagesString = (from x in pages select x.Join(",")).Join(" ; "); Console.WriteLine("Expected : " + expectedString); Console.WriteLine("Pages : " + pagesString); Assert.That(pagesString, Is.EqualTo(expectedString)); } } 

雅各布写道

对于Linq的Skip and Take方法来说,这似乎是一个理想的用例,具体取决于你想要通过分块实现的目标。 这是完全未经测试的,从未在IDE代码中输入,但您的方法可能看起来像这样。

 private List> ChunkifyTable(DataTable table, int chunkSize) { List> chunks = new List>(); for (int i = 0; i < table.Rows.Count / chunkSize; i++) { chunks.Add(table.Rows.Skip(i * chunkSize).Take(chunkSize).ToList()); } return chunks; } 

感谢Jacob - 对我有用,但我认为你的例子中的测试应该是<= not <。 如果使用<并且行数小于chunkSize ,则永远不会输入循环。 类似地,不捕获最后的部分块,仅捕获完整的块。 正如你所说,这个例子是未经测试的,所以这只是一个FYI,以防其他人逐字使用你的代码;-)