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

nmpmafwu  于 2022-12-20  发布在  其他
关注(0)|答案(6)|浏览(145)

**更新:**这里有一个similar question

假设我有一个DataTable,里面有几千个DataRows
我想将表拆分为多个较小的行块以进行处理。
我认为C#3改进了处理数据的能力可能会有所帮助。
这是我目前掌握的 backbone :

DataTable Table = GetTonsOfData();

// Chunks should be any IEnumerable<Chunk> type
var Chunks = ChunkifyTableIntoSmallerChunksSomehow; // ** help here! **

foreach(var Chunk in Chunks)
{
   // Chunk should be any IEnumerable<DataRow> type
   ProcessChunk(Chunk);
}

对于ChunkifyTableIntoSmallerChunksSomehow应该用什么来代替有什么建议吗?
我真的很感兴趣,有人会如何使用访问C#3工具来完成这一点。如果尝试应用这些工具是不合适的,请解释!
更新3(修改了分块,因为我真的想要表,而不是ienumerables;使用扩展方法--谢谢Jacob):
最终实施:
处理分块的扩展方法:

public static class HarenExtensions
{
    public static IEnumerable<DataTable> 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;
    }
}
i34xakig

i34xakig1#

这段代码可读性很强,而且只遍历序列一次,可能会避免重复冗余Skip()/Take()调用所带来的相当糟糕的性能特征:

public IEnumerable<IEnumerable<DataRow>> Chunkify(DataTable table, int size)
{
    List<DataRow> chunk = new List<DataRow>(size);

    foreach (var row in table.Rows)
    {
        chunk.Add(row);
        if (chunk.Count == size)
        {
            yield return chunk;
            chunk = new List<DataRow>(size);
        }
    }

    if(chunk.Any()) yield return chunk;
}
tct7dpnv

tct7dpnv2#

这看起来是Linq的Skip和Take方法的理想用例,这取决于你想用分块实现什么。这是完全未经测试的,从来没有在IDE代码中输入过,但是你的方法可能看起来像这样。

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

s6fujrry3#

下面是一个可能奏效的方法:

public static class Extensions
{
    public static IEnumerable<IEnumerable<T>> InPages<T>(this IEnumerable<T> 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<T> InPagesInternal<T>(IEnumerator<T> 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<T>(this IEnumerable<T> 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));
    }
}
omqzjyyz

omqzjyyz4#

雅各布写道
这看起来是Linq的Skip和Take方法的理想用例,这取决于你想用分块实现什么。这是完全未经测试的,从来没有在IDE代码中输入过,但是你的方法可能看起来像这样。

private List<List<DataRow>> ChunkifyTable(DataTable table, int chunkSize)
{
    List<List<DataRow>> chunks = new List<List<DaraRow>>();
    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 *,那么循环就不会进入。同样的,最后一个部分块也不会被捕获,只有完整的块。正如你所说的,这个例子是未经测试的,等等,所以这只是一个提示,以防别人逐字使用你的代码; -)

kmpatx3s

kmpatx3s5#

这里有一个完全不同的方法,没有为块分配内存。

public static IEnumerable<IEnumerable<DataRow>> Chunkify(
    this DataTable dataTable, int chunkSize)
{
    for (int i = 0; i < dataTable.Rows.Count; i += chunkSize)
    {
        yield return GetChunk(i, Math.Min(i + chunkSize, dataTable.Rows.Count));
    }
    IEnumerable<DataRow> GetChunk(int from, int toExclusive)
    {
        for (int j = from; j < toExclusive; j++)
        {
            yield return dataTable.Rows[j];
        }
    }
}

用法示例:

var dataTable = GetTonsOfData();
foreach (var chunk in dataTable.Chunkify(1000))
{
    Console.WriteLine($"Processing chunk of {chunk.Count()} rows");
    foreach (var dataRow in chunk)
    {
        Console.WriteLine(dataRow[0]);
    }
}
rryofs0p

rryofs0p6#

.NET(Core)6引入了Chunk扩展方法,可用于轻松地将DataTable拆分为多个批:

IEnumerable<DataRow[]> chunks=myTable.AsEnumerable()
                                     .Chunk(1000);

在早期版本MoreLINQ's Batch中,扩展方法也可用于执行相同操作:

IEnumerable<IEnumerable<DataRow>> chunks=myTable.AsEnumerable()
                                                .Batch(1000);

这两种方法都可以用来将DataTable拆分为更小的单元。下面的扩展方法使用LoadRows帮助器来提取行加载代码,从而实现这一目的:

public static IEnumerable<DataTable> Chunk(this DataTable source, int size)
{
    ArgumentNullException.ThrowIfNull(source);
    foreach (var chunk in source.AsEnumerable().Chunk(size))
    {
        var chunkTable = source.Clone();
        chunkTable.MinimumCapacity = size;
        chunkTable.LoadRows(chunk);
        yield return chunkTable;
    }
}

public static DataTable LoadRows(this DataTable table, IEnumerable<DataRow> rows)
{
    ArgumentNullException.ThrowIfNull(table);
    ArgumentNullException.ThrowIfNull(rows);
    
    foreach (var row in rows)
    {
        table.ImportRow(row);
    }

    return table;
}

ArgumentNullException.ThrowIfNull(source);是.NET核心的另一个附加功能,如果参数为空,则使用参数名抛出ArgumentNullException
最后,chunkTable.MinimumCapacity = size;用于为每个表的行保留空间,以避免重新分配

相关问题