.claude/skills/bulk-operation-batching/SKILL.md
Use when performing batch inserts, updates, or deletes on large datasets, running scheduled cleanup or archive jobs, or when EF Core's default SaveChangesAsync is generating thousands of individual SQL statements causing timeouts or lock contention. Covers EF Core 7+ ExecuteUpdate/ExecuteDelete for single-statement bulk ops, chunked SaveChanges with ChangeTracker.Clear to prevent memory growth, and BulkExtensions for high-volume import/upsert scenarios. Domain: Data Access, Performance. Level: Intermediate. Tags: bulk, batch, ef-core, performance, execute-update, execute-delete.
npx skillsauth add klod68/littlerae bulk-operation-batchingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
EF Core's default SaveChangesAsync generates one SQL statement per entity. Updating 10,000 rows means 10,000 UPDATE statements — causing timeout, lock contention, and excessive network round-trips.
// Bulk update — single SQL statement, no entity loading
await dbContext.Orders
.Where(o => o.Status == OrderStatus.Pending && o.CreatedAtUtc < cutoff)
.ExecuteUpdateAsync(setters => setters
.SetProperty(o => o.Status, OrderStatus.Expired)
.SetProperty(o => o.UpdatedAtUtc, DateTime.UtcNow),
ct);
// Bulk delete — single SQL statement
await dbContext.Orders
.Where(o => o.Status == OrderStatus.Expired && o.CreatedAtUtc < archiveCutoff)
.ExecuteDeleteAsync(ct);
public static async Task SaveInBatchesAsync<T>(
DbContext dbContext,
IReadOnlyList<T> entities,
int batchSize = 500,
CancellationToken ct = default) where T : class
{
ArgumentNullException.ThrowIfNull(dbContext);
ArgumentNullException.ThrowIfNull(entities);
for (var i = 0; i < entities.Count; i += batchSize)
{
var batch = entities.Skip(i).Take(batchSize);
dbContext.Set<T>().AddRange(batch);
await dbContext.SaveChangesAsync(ct);
dbContext.ChangeTracker.Clear(); // Prevent memory growth
}
}
// EFCore.BulkExtensions package
await dbContext.BulkInsertAsync(orders, cancellationToken: ct);
await dbContext.BulkUpdateAsync(orders, cancellationToken: ct);
await dbContext.BulkDeleteAsync(orders, cancellationToken: ct);
// Upsert (insert or update)
await dbContext.BulkInsertOrUpdateAsync(orders, cancellationToken: ct);
| Scenario | Approach | Why |
|----------|----------|-----|
| Update/delete by condition (no entity needed) | ExecuteUpdate/ExecuteDelete | Single SQL, no loading, fastest |
| Insert 100–1,000 rows | Chunked SaveChangesAsync | EF tracks entities, moderate performance |
| Insert/update 10,000+ rows | BulkExtensions | Optimized SQL, minimal round-trips |
| Import from external source | BulkExtensions or raw SQL | Volume too high for EF tracking |
| Soft delete by flag | ExecuteUpdate | Single statement sets IsDeleted = true |
public async Task ProcessInChunksAsync(
IReadOnlyList<Guid> ids,
int chunkSize,
CancellationToken ct)
{
foreach (var chunk in ids.Chunk(chunkSize))
{
await using var scope = _scopeFactory.CreateAsyncScope();
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var items = await dbContext.Orders
.Where(o => chunk.Contains(o.Id))
.ToListAsync(ct);
foreach (var item in items)
{
item.Process();
}
await dbContext.SaveChangesAsync(ct);
}
}
SaveChangesAsync is fine)ExecuteUpdate/ExecuteDelete bypass change tracker: No SaveChanges, no interceptors, no domain events. If you need side effects, load and process entities individually.ChangeTracker.Clear() after each batch to prevent memory growth and performance degradation.ExecuteUpdate/ExecuteDelete runs in a single SQL statement — inherently atomic. Chunked operations need an explicit transaction if all-or-nothing is required.ExecuteUpdate does not check concurrency tokens. If optimistic concurrency is needed, load entities first.tools
Use when cross-cutting concerns (logging, metrics, validation, authorization) are tangled into command handlers or service methods, when building database command pipelines with reorderable concerns, or when HTTP client pipelines or message handlers need composable, independently-replaceable processing stages. Covers ICommandInterceptor interface, InterceptorPipeline with reverse-chain construction, zero-cost Empty sentinel to skip overhead when no interceptors are registered, and ConfigureAwait(false) discipline for library code. Domain: Architecture, Cross-Cutting Concerns. Level: Intermediate. Tags: interceptor, pipeline, middleware, decorator, cross-cutting-concerns.
development
Use when writing integration tests for Razor Pages, MVC, or Minimal API applications to validate routing, middleware, page rendering, and HTTP behavior without a browser or live server, or when adding fast smoke tests to a CI pipeline. Covers WebApplicationFactory<Program> setup with public partial class Program, in-memory test server, AngleSharp HTML parsing, CSS selector assertions, redirect and status code testing, and a shared static fixture pattern for minimal per-test startup overhead. Domain: Testing, ASP.NET Core. Level: Intermediate. Tags: integration-testing, webapplicationfactory, razor-pages, anglesharp, http-testing.
development
Use when designing indexes for new tables, diagnosing slow queries that are not using indexes efficiently, reviewing index fragmentation and maintenance, or when the current indexing strategy results in key lookups, table scans, or missing index warnings. Covers clustered index key selection (narrow, unique, ever-increasing), non-clustered index design for query patterns, covering indexes with INCLUDE columns, filtered indexes for subset queries, composite index column ordering, DMV-based monitoring for missing and unused indexes, and rebuild vs reorganize maintenance thresholds. Domain: Database, Performance. Level: Intermediate. Tags: index, sql-server, covering-index, filtered-index, performance, dmv, maintenance.
development
Use when building a searchable in-memory catalog or registry for documentation sites, admin panels, or type/API browsers where you need keyword matching, fuzzy search, and ranked results without an external search engine or database. Covers RegistryService with weighted scoring across name, description, keywords, and method names; Levenshtein fuzzy matching; synonym expansion; category and subcategory filtering; and singleton DI registration for datasets of hundreds to low thousands of items. Domain: Search, Data Access Patterns. Level: Intermediate. Tags: search, registry, fuzzy-matching, in-memory, catalog, filtering.