Large Data CRUD in .NET: Batching Techniques to Avoid Out of Memory
BatchHelper
public static class BatchHelper { public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int size) { using var e = source.GetEnumerator(); while (e.MoveNext()) yield return GetBatch(e, size); } private static IEnumerable<T> GetBatch<T>(IEnumerator<T> e, int size) { do yield return e.Current; while (--size > 0 && e.MoveNext()); } }
Batched Create (Insert)
public async Task BulkInsertAsync(IEnumerable<MyModel> data, MyDbContext dbContext) { foreach (var batch in data.Batch(1000)) { dbContext.MyModels.AddRange(batch); await dbContext.SaveChangesAsync(); } }
Batched Update
public async Task BulkUpdateAsync(IEnumerable<MyModel> updatedData, MyDbContext dbContext) { foreach (var batch in updatedData.Batch(1000)) { dbContext.MyModels.UpdateRange(batch); await dbContext.SaveChangesAsync(); } }
⚠️ If you're using detached objects or want to attach only modified values, you'll need to Attach() them and set their state to Modified.
// Alternative: attach manually foreach (var item in batch) { dbContext.MyModels.Attach(item); dbContext.Entry(item).State = EntityState.Modified; }
Batched Delete
public async Task BulkDeleteAsync(IEnumerable<int> idsToDelete, MyDbContext dbContext) { foreach (var batch in idsToDelete.Batch(1000)) { var records = await dbContext.MyModels .Where(m => batch.Contains(m.Id)) .ToListAsync(); dbContext.MyModels.RemoveRange(records); await dbContext.SaveChangesAsync(); } }