NP0003: SaveChanges runs once per element (N+1 write)
Default severity: Warning · Code fix: moves the call after the loop
What it detects
SaveChanges or SaveChangesAsync called once per element, directly or through a helper:
foreach (var order in orders)
{
order.Total = 0;
await db.SaveChangesAsync(); // NP0003
}
A transaction started once per element (Database.BeginTransaction, BeginTransactionAsync) is reported the same way:
its begin and commit are round trips, and each element commits on its own. Start one transaction around the loop.
ABP's repository writes with autoSave: true (InsertAsync, UpdateAsync, DeleteAsync, and their Many forms)
save at once, so they are reported per element too; without it the unit of work saves everything once at its end.
Why it matters
Every call is a round trip and a transaction. Saving once after the loop sends the same changes in one batch (EF Core batches the statements) inside one transaction, which is also what most code means: all or nothing.
How to fix it
Call it once after the loop (the code fix does this when the call is a statement directly in the loop body), or replace load-modify-save with one set-based statement:
await db.Orders.Where(o => ids.Contains(o.Id)).ExecuteUpdateAsync(s => s.SetProperty(o => o.Total, 0));
If each element really must be committed on its own (the side effect after it must not be lost when a later element fails), suppress the warning there and say why.
What it leaves alone
foreach over Chunk(...): saving once per batch is the fix for very large loops.