Como percorrer IEnumerable em lotes

Estou desenvolvendo um programa ac # que possui um “IEnumerable users” que armazena os ids de 4 milhões de usuários. Eu preciso percorrer o Ienummerable e extrair um lote 1000 ids cada vez para executar algumas operações em outro método.

Como eu extrair 1000 ids de cada vez a partir do início do Ienumerable … fazer alguma outra coisa, em seguida, buscar o próximo lote de 1000 e assim por diante?

Isso é possível?

Parece que você precisa usar os methods Skip e Take do seu object. Exemplo:

users.Skip(1000).Take(1000) 

Isso iria pular o primeiro 1000 e levar o próximo 1000. Você só precisa aumentar a quantidade ignorada com cada chamada

Você poderia usar uma variável inteira com o parâmetro Skip e você pode ajustar quanto é pulado. Você pode então chamá-lo em um método.

 public IEnumerable GetBatch(int pageNumber) { return users.Skip(pageNumber * 1000).Take(1000); } 

Você pode usar o operador Batch do MoreLINQ (disponível no NuGet):

 foreach(IEnumerable batch in users.Batch(1000)) // use batch 

Se o uso simples da biblioteca não for uma opção, você poderá reutilizar a implementação:

 public static IEnumerable> Batch( this IEnumerable source, int size) { T[] bucket = null; var count = 0; foreach (var item in source) { if (bucket == null) bucket = new T[size]; bucket[count++] = item; if (count != size) continue; yield return bucket.Select(x => x); bucket = null; count = 0; } // Return the last bucket with all remaining elements if (bucket != null && count > 0) yield return bucket.Take(count); } 

BTW para desempenho, você pode simplesmente retornar o bucket sem chamar Select(x => x) . Select é otimizado para matrizes, mas o delegado do seletor ainda seria chamado em cada item. Então, no seu caso, é melhor usar

 yield return bucket; 

A maneira mais fácil de fazer isso é provavelmente usar o método GroupBy no LINQ:

 var batches = myEnumerable .Select((x, i) => new { x, i }) .GroupBy(p => (pi / 1000), (p, i) => px); 

Mas para uma solução mais sofisticada, veja esta postagem do blog sobre como criar seu próprio método de extensão para fazer isso. Duplicado aqui para a posteridade:

 public static IEnumerable> Batch(this IEnumerable collection, int batchSize) { List nextbatch = new List(batchSize); foreach (T item in collection) { nextbatch.Add(item); if (nextbatch.Count == batchSize) { yield return nextbatch; nextbatch = new List(); // or nextbatch.Clear(); but see Servy's comment below } } if (nextbatch.Count > 0) yield return nextbatch; } 

tente usar isso:

  public static IEnumerable> Batch( this IEnumerable source, int batchSize) { var batch = new List(); foreach (var item in source) { batch.Add(item); if (batch.Count == batchSize) { yield return batch; batch = new List(); } } if (batch.Any()) yield return batch; } 

e usar a function acima:

 foreach (var list in Users.Batch(1000)) { } 

Você pode conseguir isso usando o método de extensão Enumerável Take and Skip. Para mais informações sobre checkout de uso linq 101

Algo assim funcionaria:

 List batch = new List(); foreach (MyClass item in items) { batch.Add(item); if (batch.Count == 1000) { // Perform operation on batch batch.Clear(); } } // Process last batch if (batch.Any()) { // Perform operation on batch } 

E você poderia generalizar isso em um método genérico, como este:

 static void PerformBatchedOperation(IEnumerable items, Action> operation, int batchSize) { List batch = new List(); foreach (T item in items) { batch.Add(item); if (batch.Count == batchSize) { operation(batch); batch.Clear(); } } // Process last batch if (batch.Any()) { operation(batch); } } 

Você pode usar o Take operator linq

Link: http://msdn.microsoft.com/fr-fr/library/vstudio/bb503062.aspx

E se

 int batchsize = 5; List colection = new List { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"}; for (int x = 0; x < Math.Ceiling((decimal)colection.Count / batchsize); x++) { var t = colection.Skip(x * batchsize).Take(batchsize); }