Apa cara bersih untuk memecah DataTable menjadi beberapa bagian dengan ukuran tetap dengan Linq?

Pembaruan: Berikut pertanyaan serupa


Misalkan saya memiliki DataTable dengan beberapa ribu DataRows di dalamnya.

Saya ingin memecah tabel menjadi beberapa baris yang lebih kecil untuk diproses.

Saya pikir peningkatan kemampuan C#3 untuk bekerja dengan data mungkin bisa membantu.

Inilah kerangka yang saya miliki sejauh ini:

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);
}

Ada saran tentang apa yang harus menggantikan ChunkifyTableIntoSmallerChunksSomehow?

Saya sangat tertarik dengan bagaimana seseorang melakukan ini dengan mengakses alat C#3. Jika upaya menerapkan alat ini tidak tepat, mohon jelaskan!


Pembaruan 3 (potongan yang direvisi karena saya benar-benar menginginkan tabel, bukan ienumerable; menggunakan metode ekstensi--terima kasih Jacob):

Implementasi akhir:

Metode ekstensi untuk menangani chunking:

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;
        }
    }
}

Contoh konsumen metode ekstensi tersebut, dengan contoh keluaran dari pengujian ad hoc:

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;
    }
}

person Michael Haren    schedule 20.04.2009    source sumber
comment
Saya tidak menggunakan ini untuk paging tetapi saya baru menyadari aplikasi paralelnya. Jika Anda menemukan duplikat bagus yang berlaku di sini, harap beri tahu saya dan saya akan menutup pertanyaannya.   -  person Michael Haren    schedule 20.04.2009
comment
Anda dapat menyejajarkannya dengan membuat metode ekstensi yang melakukan hal di atas. Kemudian Anda bisa menggunakan var Chunks = from chunk di table.Chunkify select chunk;   -  person Jacob Proffitt    schedule 20.04.2009


Jawaban (5)


Ini sepertinya kasus penggunaan yang ideal untuk metode Lewati dan Ambil Linq, tergantung pada apa yang ingin Anda capai dengan pemotongan tersebut. Ini benar-benar belum teruji, tidak pernah dimasukkan dalam kode IDE, namun metode Anda mungkin terlihat seperti ini.

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;
}
person Jacob Proffitt    schedule 20.04.2009
comment
@Jacob: terima kasih atas sarannya. Saya membuat tabel pengembalian ini dan mengimplementasikan IEnumerable dengan kata kunci hasil. Saya juga mengubahnya menjadi metode penyuluhan seperti yang Anda sarankan. Berfungsi dengan baik! - person Michael Haren; 20.04.2009
comment
Ini rusak untuk kumpulan data yang tidak habis dibagi chunkSize. 1/10 = 0 saat menggunakan int, jadi bagian terakhir tabel terpotong. - person N_A; 20.02.2015
comment
Ya, Anda ada benarnya. Setidaknya, ketika jumlah baris kurang dari ukuran potongan, ia akan gagal pada pengujian pertama (karena i akan menjadi nol dan bukan ‹ nol sehingga perulangan for gagal masuk). i bertambah setelah evaluasi, jadi 11/10 akan berjalan dua kali dan mengambil semua 11. - person Jacob Proffitt; 20.02.2015
comment
berikut adalah kode versi c# dan vb.net tanpa menggunakan LINQ yang berfungsi untuk saya membagi tabel data menjadi beberapa bagian - person Jeff D; 13.04.2017

Ini cukup mudah dibaca dan hanya mengulangi urutannya satu kali, mungkin menyelamatkan Anda dari karakteristik kinerja yang agak buruk dari panggilan Skip() / Take() yang berulang-ulang:

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;
}
person mqp    schedule 20.04.2009
comment
Ini tentunya merupakan pendekatan yang paling mudah dibaca, namun memaksa pembuatan daftar dalam memori untuk setiap bagian. Itu masuk akal untuk ukuran bongkahan kecil tetapi tidak untuk yang besar. Selain itu, saya akan menghapus bagian tersebut daripada membuat yang baru setiap saat. Saya pikir solusi terbaik mungkin akan membuat sebuah iterator yang memiliki sub iterator, tapi itu pasti tidak akan mudah dibaca. - person Damian Powell; 26.09.2009

Inilah pendekatan yang mungkin berhasil:

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));
    }
}
person Damian Powell    schedule 26.09.2009

tulis Yakub

Ini sepertinya kasus penggunaan yang ideal untuk metode Lewati dan Ambil Linq, tergantung pada apa yang ingin Anda capai dengan pemotongan tersebut. Ini benar-benar belum teruji, tidak pernah dimasukkan dalam kode IDE, namun metode Anda mungkin terlihat seperti ini.

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;
}

Terima kasih untuk ini Jacob - berguna bagi saya, tetapi menurut saya tes dalam contoh Anda seharusnya ‹= tidak ‹. Jika Anda menggunakan ‹ dan jumlah barisnya kurang dari chunkSize maka loop tidak akan pernah dimasukkan. Demikian pula, potongan sebagian terakhir tidak ditangkap, hanya potongan penuh. Seperti yang telah Anda nyatakan, contohnya belum diuji, dll jadi ini hanya sekedar FYI kalau-kalau ada orang lain yang menggunakan kode Anda kata demi kata ;-)

person David Clarke    schedule 28.04.2010

Ini adalah pendekatan yang sangat berbeda. Tidak ada memori yang dialokasikan untuk potongan tersebut.

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];
        }
    }
}

Contoh penggunaan:

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]);
    }
}
person Theodor Zoulias    schedule 25.07.2019