From b5cc70c08c0cabfcdd34af110bcd3736e43a54d2 Mon Sep 17 00:00:00 2001 From: Kleidukos Date: Sun, 16 Aug 2026 14:11:18 +0200 Subject: [PATCH] fix(news): use CreateAsyncScope for Playwright service and gracefully handle DbUpdateException on duplicate URLs --- FinlyticNews/Services/NewsDbService.cs | 19 ++++++++++++++++--- .../Services/NewsScraperBackgroundService.cs | 6 +++--- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/FinlyticNews/Services/NewsDbService.cs b/FinlyticNews/Services/NewsDbService.cs index 8523aec..f1fd628 100644 --- a/FinlyticNews/Services/NewsDbService.cs +++ b/FinlyticNews/Services/NewsDbService.cs @@ -158,10 +158,23 @@ public class NewsDbService : INewsDbService await _context.SaveChangesAsync(); await _finlyticLogger.LogDebugAsync(SettingKeys.NewsChannel, "[Lifecycle] Registered new article with status 'Pending'. ID: {Id}, Url: {Url}", article.Id, trimmedUrl); } - catch (DbUpdateConcurrencyException) + catch (DbUpdateException ex) { - await _finlyticLogger.LogWarningAsync(SettingKeys.NewsChannel, "[NewsChannel] Concurrency hit during insert for URL: {Url}. Fetching existing fallback.", trimmedUrl); - return await _context.NewsArticles.FirstAsync(a => a.SourceUrl == trimmedUrl); + _context.Entry(article).State = EntityState.Detached; + if (article.MatchedAssets != null) + { + foreach (var match in article.MatchedAssets) + { + _context.Entry(match).State = EntityState.Detached; + } + } + await _finlyticLogger.LogWarningAsync(SettingKeys.NewsChannel, "[NewsChannel] Unique constraint or concurrency hit during insert for URL: {Url}. Fetching existing fallback. ({Message})", trimmedUrl, ex.InnerException?.Message ?? ex.Message); + var existing = await _context.NewsArticles.AsNoTracking().FirstOrDefaultAsync(a => a.SourceUrl == trimmedUrl); + if (existing != null) + { + return existing; + } + throw; } return article; diff --git a/FinlyticNews/Services/NewsScraperBackgroundService.cs b/FinlyticNews/Services/NewsScraperBackgroundService.cs index 52503ec..7928ee6 100644 --- a/FinlyticNews/Services/NewsScraperBackgroundService.cs +++ b/FinlyticNews/Services/NewsScraperBackgroundService.cs @@ -61,7 +61,7 @@ public class NewsScraperBackgroundService : BackgroundService { try { - using var scope = _scopeFactory.CreateScope(); + await using var scope = _scopeFactory.CreateAsyncScope(); var settings = scope.ServiceProvider.GetRequiredService(); bool enabled = await settings.GetSettingAsync(SettingKeys.EnableAutoScraping, stoppingToken); @@ -82,7 +82,7 @@ public class NewsScraperBackgroundService : BackgroundService int intervalMinutes = 15; try { - using var scope = _scopeFactory.CreateScope(); + await using var scope = _scopeFactory.CreateAsyncScope(); var settings = scope.ServiceProvider.GetRequiredService(); intervalMinutes = await settings.GetSettingAsync(SettingKeys.ScrapeIntervalMinutes, stoppingToken); } @@ -107,7 +107,7 @@ public class NewsScraperBackgroundService : BackgroundService private async Task RunScrapingCycleAsync(CancellationToken stoppingToken) { - using var scope = _scopeFactory.CreateScope(); + await using var scope = _scopeFactory.CreateAsyncScope(); var dbService = scope.ServiceProvider.GetRequiredService(); var discoveryService = scope.ServiceProvider.GetRequiredService(); var scraperService = scope.ServiceProvider.GetRequiredService();