Optimize transaction preview duplicate checking for large imports
Improve performance when uploading thousands of transactions by loading all existing transactions into memory once for duplicate detection, rather than running individual database queries for each transaction. This reduces database calls from O(n) to O(1) for n transactions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -255,6 +255,11 @@ namespace MoneyMap.Pages
|
||||
var previewItems = new List<TransactionPreview>();
|
||||
var addedInThisBatch = new HashSet<TransactionKey>();
|
||||
|
||||
// Load all existing transactions into memory for fast duplicate checking
|
||||
var existingTransactions = await _db.Transactions
|
||||
.Select(t => new TransactionKey(t.Date, t.Amount, t.Name, t.Memo, t.AccountId, t.CardId))
|
||||
.ToHashSetAsync();
|
||||
|
||||
using var reader = new StreamReader(csvStream);
|
||||
using var csv = new CsvReader(reader, new CsvConfiguration(CultureInfo.InvariantCulture)
|
||||
{
|
||||
@@ -279,7 +284,8 @@ namespace MoneyMap.Pages
|
||||
var transaction = MapToTransaction(row, paymentResolution);
|
||||
var key = new TransactionKey(transaction);
|
||||
|
||||
bool isDuplicate = addedInThisBatch.Contains(key) || await IsDuplicate(transaction);
|
||||
// Fast in-memory duplicate checking
|
||||
bool isDuplicate = addedInThisBatch.Contains(key) || existingTransactions.Contains(key);
|
||||
|
||||
previewItems.Add(new TransactionPreview
|
||||
{
|
||||
@@ -415,7 +421,7 @@ namespace MoneyMap.Pages
|
||||
return PaymentResolutionResult.SuccessAccount(account.Id, account.Last4);
|
||||
}
|
||||
|
||||
private async Task<PaymentResolutionResult> ResolveAutomaticallyAsync(string? memo, ImportContext context)
|
||||
private Task<PaymentResolutionResult> ResolveAutomaticallyAsync(string? memo, ImportContext context)
|
||||
{
|
||||
// Extract last4 from both memo and filename
|
||||
var last4FromFile = CardIdentifierExtractor.FromFileName(context.FileName);
|
||||
@@ -425,26 +431,26 @@ namespace MoneyMap.Pages
|
||||
if (!string.IsNullOrWhiteSpace(last4FromMemo))
|
||||
{
|
||||
var result = TryResolveByLast4(last4FromMemo, context);
|
||||
if (result != null) return result;
|
||||
if (result != null) return Task.FromResult(result);
|
||||
}
|
||||
|
||||
// PRIORITY 2: Fall back to filename (for account-level CSVs or when memo has no card)
|
||||
if (!string.IsNullOrWhiteSpace(last4FromFile))
|
||||
{
|
||||
var result = TryResolveByLast4(last4FromFile, context);
|
||||
if (result != null) return result;
|
||||
if (result != null) return Task.FromResult(result);
|
||||
}
|
||||
|
||||
// Nothing found - error
|
||||
var searchedLast4 = last4FromMemo ?? last4FromFile;
|
||||
if (string.IsNullOrWhiteSpace(searchedLast4))
|
||||
{
|
||||
return PaymentResolutionResult.Failure(
|
||||
"Couldn't determine card or account from memo or file name. Choose an account manually.");
|
||||
return Task.FromResult(PaymentResolutionResult.Failure(
|
||||
"Couldn't determine card or account from memo or file name. Choose an account manually."));
|
||||
}
|
||||
|
||||
return PaymentResolutionResult.Failure(
|
||||
$"Couldn't find account or card with last4 '{searchedLast4}'. Choose an account manually.");
|
||||
return Task.FromResult(PaymentResolutionResult.Failure(
|
||||
$"Couldn't find account or card with last4 '{searchedLast4}'. Choose an account manually."));
|
||||
}
|
||||
|
||||
private PaymentResolutionResult? TryResolveByLast4(string last4, ImportContext context)
|
||||
|
||||
Reference in New Issue
Block a user