Come eseguire il ciclo di IEnumerable in batch

Sto sviluppando il programma ac # che ha un “IEnumerable users” che memorizza gli ID di 4 milioni di utenti. Ho bisogno di scorrere l’Ienummerable ed estrarre un batch 1000 id ogni volta per eseguire alcune operazioni in un altro metodo.

Come faccio a estrarre 1000 id alla volta dall’inizio di Ienumerable … fai qualcos’altro quindi recupera il prossimo batch di 1000 e così via?

È ansible?

Sembra che tu debba usare i metodi Skip e Take del tuo object. Esempio:

users.Skip(1000).Take(1000) 

questo saltava il primo 1000 e prendeva il successivo 1000. Avresti solo bisogno di aumentare la quantità saltata con ogni chiamata

È ansible utilizzare una variabile intera con il parametro per Salta e regolare la quantità saltata. Puoi quindi chiamarlo in un metodo.

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

Puoi utilizzare l’operatore Batch di MoreLINQ (disponibile da NuGet):

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

Se l’utilizzo semplice della libreria non è un’opzione, è ansible riutilizzare l’implementazione:

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

A proposito di prestazioni, puoi semplicemente restituire il bucket senza chiamare Select(x => x) . Select è ottimizzato per gli array, ma il delegato del selettore verrà comunque richiamato su ciascun elemento. Quindi, nel tuo caso è meglio usare

 yield return bucket; 

Il modo più semplice per farlo è probabilmente solo utilizzare il metodo GroupBy in LINQ:

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

Ma per una soluzione più sofisticata, consulta questo post sul blog su come creare il tuo metodo di estensione per farlo. Duplicato qui per i posteri:

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

prova a usare questo:

  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 usare la funzione precedente:

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

È ansible farlo utilizzando il metodo di estensione Take and Skip Enumerable. Per ulteriori informazioni sull’utilizzo checkout linq 101

Qualcosa del genere funzionerebbe:

 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 potresti generalizzare questo in un metodo generico, come questo:

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

È ansible utilizzare l’ Take operator linq

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

Che ne dite di

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