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; }