From 0f3ad89c59c725d60e9c4823e4d8d96e9e0d38c2 Mon Sep 17 00:00:00 2001 From: Catherine Renelle <32781029+catrenelle@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:12:20 -0500 Subject: [PATCH] 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 --- src/Purrse.Api/Services/LoanService.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Purrse.Api/Services/LoanService.cs b/src/Purrse.Api/Services/LoanService.cs index df2bb96..149713b 100644 --- a/src/Purrse.Api/Services/LoanService.cs +++ b/src/Purrse.Api/Services/LoanService.cs @@ -64,13 +64,14 @@ public class LoanService : ILoanService public async Task 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();