A money transfer credits one account and debits another by the same amount. When balances occupy separate database rows, both updates must succeed together: a database transaction commits both or rolls them back. This protects against a failed step, lost connection, or application crash. section 11.2.1
When a transfer fails
A naive `TransferAsync` credits the recipient and saves that change before debiting the sender. If the sender lacks funds, `DebitAsync` throws, but the credit has already been saved. A test starting both accounts at 100 and attempting to transfer 120 exposes the invalid balances. section 11.2.1
Listing 11.11 A transactional version of the BankRepository.TransferAsync
public async Task TransferAsync(
string from, string to, decimal amt)
{
using var tx = await
_ctxt.Database.BeginTransactionAsync(); #1
try
{
await CreditAsync(to, amt); #2
await DebitAsync(from, amt);
await tx.CommitAsync(); #3
}
catch #4
{
await tx.RollbackAsync(); #5
_ctxt.ChangeTracker.Clear(); #6
throw; #7
}
}
The transactional version begins a transaction before either update, commits after both succeed, and rolls back on failure. It also clears EF Core’s change tracker before rethrowing so tracked account state does not retain the failed changes. section 11.2.1
