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 <noreply@anthropic.com>
This commit is contained in:
2026-02-05 23:20:08 -05:00
parent fbe52f72d6
commit 6f63f36df0

View File

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