updateOne method
update, but insisting that exactly one row changed.
The write runs inside a transaction, so a count that turns out wrong leaves nothing behind.
The three outcomes get three different exceptions, because they mean three different things to whoever has to fix them:
- No row — MssqlRowNotFoundException. The write targets one row by
its primary key, so zero means that row is not there, or a scope
hides it; the same conclusion
getByIdreports. - More than one row — StateError naming the key. A primary key cannot match twice, so this says the declared key does not identify a row: a schema or binding problem, not a data one. Reporting it as an affected-row count would point at the wrong thing.
- Exactly one — the write stands.
Either failure rolls the write back before the exception leaves.
Implementation
Future<void> updateOne(TRow row, {Set<String>? columns}) async {
_requireKey('updateOne');
_checkDecimalAgreement();
if (columns != null) _checkColumnSubset(columns);
final values = _stampUpdate(
_writableValues(row, only: columns, excludeKey: true),
);
if (values.isEmpty) {
throw StateError(
'Updating ${binding.qualifiedName} would write no columns.',
);
}
final engine = await _engine();
try {
await engine.guard(
operation: 'updateOne',
expected: 1,
write: (scoped) => _updateKeyed(row, values, engine: scoped),
);
} on MssqlAffectedRowsException catch (error) {
final source = binding.toColumns(row);
final key = <String, Object?>{
for (final name in binding.primaryKey) name: source[name],
};
if (error.actual == 0) {
throw MssqlRowNotFoundException(binding.qualifiedName, key);
}
throw StateError(
'updateOne on ${binding.qualifiedName} changed ${error.actual} rows '
'for key $key, so the declared primary key '
'(${binding.primaryKey.join(', ')}) does not identify a single row '
'in this table. That is a schema or a binding problem rather than a '
'data one, which is why it is not reported as an affected-row count. '
'The write was rolled back.',
);
}
}