fix(news): use CreateAsyncScope for Playwright service and gracefully handle DbUpdateException on duplicate URLs

This commit is contained in:
2026-08-16 14:11:18 +02:00
parent 2ba54e8057
commit b5cc70c08c
2 changed files with 19 additions and 6 deletions
+16 -3
View File
@@ -158,10 +158,23 @@ public class NewsDbService : INewsDbService
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
await _finlyticLogger.LogDebugAsync(SettingKeys.NewsChannel, "[Lifecycle] Registered new article with status 'Pending'. ID: {Id}, Url: {Url}", article.Id, trimmedUrl); 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); _context.Entry(article).State = EntityState.Detached;
return await _context.NewsArticles.FirstAsync(a => a.SourceUrl == trimmedUrl); 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; return article;
@@ -61,7 +61,7 @@ public class NewsScraperBackgroundService : BackgroundService
{ {
try try
{ {
using var scope = _scopeFactory.CreateScope(); await using var scope = _scopeFactory.CreateAsyncScope();
var settings = scope.ServiceProvider.GetRequiredService<ISettingsService>(); var settings = scope.ServiceProvider.GetRequiredService<ISettingsService>();
bool enabled = await settings.GetSettingAsync(SettingKeys.EnableAutoScraping, stoppingToken); bool enabled = await settings.GetSettingAsync(SettingKeys.EnableAutoScraping, stoppingToken);
@@ -82,7 +82,7 @@ public class NewsScraperBackgroundService : BackgroundService
int intervalMinutes = 15; int intervalMinutes = 15;
try try
{ {
using var scope = _scopeFactory.CreateScope(); await using var scope = _scopeFactory.CreateAsyncScope();
var settings = scope.ServiceProvider.GetRequiredService<ISettingsService>(); var settings = scope.ServiceProvider.GetRequiredService<ISettingsService>();
intervalMinutes = await settings.GetSettingAsync(SettingKeys.ScrapeIntervalMinutes, stoppingToken); intervalMinutes = await settings.GetSettingAsync(SettingKeys.ScrapeIntervalMinutes, stoppingToken);
} }
@@ -107,7 +107,7 @@ public class NewsScraperBackgroundService : BackgroundService
private async Task RunScrapingCycleAsync(CancellationToken stoppingToken) private async Task RunScrapingCycleAsync(CancellationToken stoppingToken)
{ {
using var scope = _scopeFactory.CreateScope(); await using var scope = _scopeFactory.CreateAsyncScope();
var dbService = scope.ServiceProvider.GetRequiredService<INewsDbService>(); var dbService = scope.ServiceProvider.GetRequiredService<INewsDbService>();
var discoveryService = scope.ServiceProvider.GetRequiredService<IArticleDiscoveryService>(); var discoveryService = scope.ServiceProvider.GetRequiredService<IArticleDiscoveryService>();
var scraperService = scope.ServiceProvider.GetRequiredService<IPlaywrightScraperService>(); var scraperService = scope.ServiceProvider.GetRequiredService<IPlaywrightScraperService>();