Summary
TDbContextEntityDataSetStore.ApplyChanges (Sources/Web/Dext.Web.EntityDataSetApi.pas) applies a batch of dataset changes one item at a time, with one SaveChanges per item and no transaction around the batch. If item 7 fails, items 1–6 are already committed, and the caller gets a per-item result list back, not a failed request.
Where
In the for i := 0 to AChanges.Count - 1 loop, each branch ends with its own ADbContext.SaveChanges (inserted, modified, deleted). The exception is caught per item and stored in ItemResult.ErrorMessage, and the loop carries on. Neither ApplyChanges nor the TEntityDataSetApi.Map<T> endpoint that calls it opens a transaction.
Why it matters
A client dataset's ApplyUpdates is usually understood as all-or-nothing: a master row plus its details, or a set of rows that only make sense together. With the current behaviour a half-applied batch is a normal outcome, and the database is left in a state no client ever asked for. It is also one round trip per item.
We learned the same lesson in our own server: batches that committed per statement looked fine until a real failure in the middle of one.
If partial success per item is intended, it may be worth documenting and making it opt-in. Something to check along the way, which we have not verified: after a failed SaveChanges, is the failed entity still in the ChangeTracker? If it is, the next item's SaveChanges would retry it.
Suggested fix
- Wrap the loop in a transaction on
ADbContext.
- Track all items first and call
SaveChanges once.
- On failure, roll back and return the failing index.
- If per-item results must stay, make that an explicit option rather than the default.
Checked against f440e79a.
🤖 Generated with Claude Code
Summary
TDbContextEntityDataSetStore.ApplyChanges(Sources/Web/Dext.Web.EntityDataSetApi.pas) applies a batch of dataset changes one item at a time, with oneSaveChangesper item and no transaction around the batch. If item 7 fails, items 1–6 are already committed, and the caller gets a per-item result list back, not a failed request.Where
In the
for i := 0 to AChanges.Count - 1loop, each branch ends with its ownADbContext.SaveChanges(inserted,modified,deleted). The exception is caught per item and stored inItemResult.ErrorMessage, and the loop carries on. NeitherApplyChangesnor theTEntityDataSetApi.Map<T>endpoint that calls it opens a transaction.Why it matters
A client dataset's
ApplyUpdatesis usually understood as all-or-nothing: a master row plus its details, or a set of rows that only make sense together. With the current behaviour a half-applied batch is a normal outcome, and the database is left in a state no client ever asked for. It is also one round trip per item.We learned the same lesson in our own server: batches that committed per statement looked fine until a real failure in the middle of one.
If partial success per item is intended, it may be worth documenting and making it opt-in. Something to check along the way, which we have not verified: after a failed
SaveChanges, is the failed entity still in theChangeTracker? If it is, the next item'sSaveChangeswould retry it.Suggested fix
ADbContext.SaveChangesonce.Checked against
f440e79a.🤖 Generated with Claude Code