Private
Public Access
1
0

Fix loan edit concurrency error with ExecuteDeleteAsync

The change tracker-based approaches (RemoveRange, Clear) both caused
DbUpdateConcurrencyException when deleting amortization entries.
Use ExecuteDeleteAsync to delete directly in the database, bypassing
the change tracker entirely.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Catherine Renelle
2026-02-14 17:12:20 -05:00
parent 6ddecd7a99
commit 0f3ad89c59
+7 -3
View File
@@ -64,13 +64,14 @@ public class LoanService : ILoanService
public async Task<LoanDetailResponse> UpdateLoanDetailAsync(Guid userId, Guid accountId, CreateLoanDetailRequest request)
{
var account = await _db.Accounts.Include(a => a.LoanDetail).ThenInclude(l => l!.AmortizationEntries)
var account = await _db.Accounts.Include(a => a.LoanDetail)
.FirstOrDefaultAsync(a => a.Id == accountId && a.UserId == userId)
?? throw new KeyNotFoundException("Account not found");
var l = account.LoanDetail ?? throw new KeyNotFoundException("No loan detail found");
l.AmortizationEntries.Clear();
// Delete old entries directly in DB, bypassing change tracker
await _db.AmortizationEntries.Where(e => e.LoanDetailId == l.Id).ExecuteDeleteAsync();
l.OriginalBalance = request.OriginalBalance;
l.InterestRate = request.InterestRate;
@@ -83,7 +84,10 @@ public class LoanService : ILoanService
var entries = GenerateAmortizationSchedule(l);
foreach (var entry in entries)
l.AmortizationEntries.Add(entry);
{
entry.LoanDetailId = l.Id;
_db.AmortizationEntries.Add(entry);
}
await _db.SaveChangesAsync();
var schedule = entries.Select(e => new AmortizationEntryResponse(e.PaymentNumber, e.PaymentDate, e.PaymentAmount, e.PrincipalAmount, e.InterestAmount, e.EscrowAmount, e.RemainingBalance)).ToList();