From 6f63f36df07e8ce39e1c7f4ea11e16fc588272a5 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Thu, 5 Feb 2026 23:20:08 -0500 Subject: [PATCH] Improve import resilience with per-message saves and duplicate handling Save after each message to isolate failures, catch and skip duplicate key violations (SQL error 2601), and clear change tracker on rollback to prevent cascading failures. Co-Authored-By: Claude Opus 4.6 --- .../Services/JsonImportService.cs | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/DiscordArchiveManager/Services/JsonImportService.cs b/src/DiscordArchiveManager/Services/JsonImportService.cs index 23bc6f5..fcc6729 100644 --- a/src/DiscordArchiveManager/Services/JsonImportService.cs +++ b/src/DiscordArchiveManager/Services/JsonImportService.cs @@ -72,20 +72,32 @@ public class JsonImportService // Upsert Channel await UpsertChannelAsync(export.Channel, export.Guild.Id); - // Process messages + // Process messages - save after each to isolate any issues var processedCount = 0; foreach (var message in export.Messages) { - if (await ProcessMessageAsync(message, export.Channel.Id, jsonFilePath, imageRoot)) + try { - processedCount++; + if (await ProcessMessageAsync(message, export.Channel.Id, jsonFilePath, imageRoot)) + { + await _context.SaveChangesAsync(); + processedCount++; + } + } + catch (DbUpdateException ex) when (ex.InnerException is Microsoft.Data.SqlClient.SqlException sqlEx && sqlEx.Number == 2601) + { + // Duplicate key - log and continue + _logger.LogWarning("Duplicate key error for message {MessageId}, skipping: {Error}", + message.Id, sqlEx.Message); + _context.ChangeTracker.Clear(); + // Re-upsert guild and channel as they were cleared + await UpsertGuildAsync(export.Guild); + await UpsertChannelAsync(export.Channel, export.Guild.Id); } } _logger.LogInformation("Processed {Count} new messages", processedCount); - await _context.SaveChangesAsync(); - // Archive the file var archivePath = _archiveService.ArchiveExport(jsonFilePath, archiveRoot); @@ -106,6 +118,9 @@ public class JsonImportService catch (Exception ex) { await transaction.RollbackAsync(); + _context.ChangeTracker.Clear(); // Clear tracked entities to prevent cascading failures + _processedMessages.Clear(); // Clear the session tracking + _addedReactions.Clear(); _logger.LogError(ex, "Error processing file, rolled back transaction: {Path}", jsonFilePath); throw; }